Databsae service refactoring. Warning, loses stars and listens stats

This commit is contained in:
emeric
2021-12-04 15:25:09 +01:00
parent 97aba4ae82
commit 0cf6b2159e
160 changed files with 6047 additions and 3477 deletions
+79 -288
View File
@@ -28,7 +28,7 @@
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
#include "Traits.hpp"
#include "IdTypeTraits.hpp"
namespace Database
{
@@ -40,8 +40,16 @@ _MBID {MBID ? MBID->getAsString() : ""}
{
}
std::size_t
Artist::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM artist");
}
std::vector<Artist::pointer>
Artist::getByName(Session& session, const std::string& name)
Artist::find(Session& session, const std::string& name)
{
session.checkSharedLocked();
@@ -53,14 +61,14 @@ Artist::getByName(Session& session, const std::string& name)
}
Artist::pointer
Artist::getByMBID(Session& session, const UUID& mbid)
Artist::find(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
return session.getDboSession().find<Artist>().where("mbid = ?").bind(std::string {mbid.getAsString()}).resultValue();
}
Artist::pointer
Artist::getById(Session& session, ArtistId id)
Artist::find(Session& session, ArtistId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Artist>().where("id = ?").bind(id).resultValue();
@@ -84,36 +92,37 @@ Artist::create(Session& session, const std::string& name, const std::optional<UU
return res;
}
template <typename T>
static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<TrackArtistLinkType> linkType)
Wt::Dbo::Query<ArtistId>
createQuery(Session& session, const Artist::FindParameters& params)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<T>(queryStr)};
query.join("track t ON t.id = t_a_l.track_id");
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id");
auto query {session.getDboSession().query<ArtistId>("SELECT DISTINCT a.id FROM artist a")};
if (params.sortMethod == ArtistSortMethod::LastWritten || params.writtenAfter.isValid() || params.linkType)
{
query.join("track t ON t.id = t_a_l.track_id");
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id");
}
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
if (params.linkType)
query.where("t_a_l.type = ?").bind(*params.linkType);
if (!keywords.empty())
if (params.writtenAfter.isValid())
query.where("t.file_last_write > ?").bind(params.writtenAfter);
if (!params.keywords.empty())
{
std::vector<std::string> clauses;
std::vector<std::string> sortClauses;
for (std::string_view keyword : keywords)
for (std::string_view keyword : params.keywords)
{
clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
query.bind("%" + escapeLikeKeyword(keyword) + "%");
}
for (std::string_view keyword : keywords)
for (std::string_view keyword : params.keywords)
{
sortClauses.push_back("a.sort_name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
query.bind("%" + escapeLikeKeyword(keyword) + "%");
@@ -122,7 +131,15 @@ createQuery(Session& session,
query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")");
}
if (!clusterIds.empty())
if (params.starringUser.isValid())
{
assert(params.scrobbler);
query.join("starred_artist s_a ON s_a.artist_id = a.id")
.where("s_a.user_id = ?").bind(params.starringUser)
.where("s_a.scrobbler = ?").bind(*params.scrobbler);
}
if (!params.clusters.empty())
{
std::ostringstream oss;
oss << "a.id IN (SELECT DISTINCT a.id FROM artist a"
@@ -132,268 +149,59 @@ createQuery(Session& session,
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (const ClusterId clusterId : clusterIds)
for (const ClusterId clusterId : params.clusters)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << params.clusters.size() << ")";
query.where(oss.str());
}
switch (params.sortMethod)
{
case ArtistSortMethod::None:
break;
case ArtistSortMethod::ByName:
query.orderBy("a.name COLLATE NOCASE");
break;
case ArtistSortMethod::BySortName:
query.orderBy("a.sort_name COLLATE NOCASE");
break;
case ArtistSortMethod::Random:
query.orderBy("RANDOM()");
break;
case ArtistSortMethod::LastWritten:
query.orderBy("t.file_last_write DESC");
break;
case ArtistSortMethod::StarredDateDesc:
assert(params.starringUser.isValid());
query.orderBy("s_a.date_time DESC");
break;
}
return query;
}
std::vector<Artist::pointer>
Artist::getAll(Session& session)
RangeResults<ArtistId>
Artist::findAllOrphans(Session& session, Range range)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<ArtistId>("SELECT DISTINCT a.id 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)")};
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session.getDboSession().find<Artist>();
return std::vector<pointer>(res.begin(), res.end());
return execQuery(query, range);
}
std::vector<Artist::pointer>
Artist::getAll(Session& session, SortMethod sortMethod)
RangeResults<ArtistId>
Artist::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
auto query {session.getDboSession().find<Artist>()};
switch (sortMethod)
{
case Artist::SortMethod::None:
break;
case Artist::SortMethod::ByName:
query.orderBy("name COLLATE NOCASE");
break;
case Artist::SortMethod::BySortName:
query.orderBy("sort_name COLLATE NOCASE");
break;
}
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = query;
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getAll(Session& session, SortMethod sortMethod, std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT a FROM Artist a", {}, {}, std::nullopt)};
switch (sortMethod)
{
case Artist::SortMethod::None:
break;
case Artist::SortMethod::ByName:
query.orderBy("a.name COLLATE NOCASE");
break;
case Artist::SortMethod::BySortName:
query.orderBy("a.sort_name COLLATE NOCASE");
break;
}
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
std::vector<Artist::pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<ArtistId>
Artist::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<ArtistId> res = session.getDboSession().query<ArtistId>("SELECT id FROM artist");
return std::vector<ArtistId>(res.begin(), res.end());
}
std::vector<ArtistId>
Artist::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusters, std::optional<TrackArtistLinkType> linkType, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto query {createQuery<ArtistId>(session, "SELECT DISTINCT a.id from artist a", clusters, {}, linkType)};
Wt::Dbo::collection<ArtistId> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<ArtistId>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getAllOrphans(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res {session.getDboSession().query<Wt::Dbo::ptr<Artist>>("SELECT DISTINCT a FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id WHERE t.id = t_a_l.track_id)")};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<ArtistId>
Artist::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<ArtistId> res = session.getDboSession().query<ArtistId>
("SELECT DISTINCT a.id FROM artist a"
" INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<ArtistId>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getByClusters(Session& session, const std::vector<ClusterId>& clusters, SortMethod sortMethod)
{
assert(!clusters.empty());
session.checkSharedLocked();
bool more{};
return getByFilter(session, clusters, {}, std::nullopt, sortMethod, std::nullopt, more);
}
std::vector<Artist::pointer>
Artist::getByFilter(Session& session,
const std::vector<ClusterId>& clusters,
const std::vector<std::string_view>& keywords,
std::optional<TrackArtistLinkType> linkType,
SortMethod sortMethod,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, keywords, linkType)};
switch (sortMethod)
{
case Artist::SortMethod::None:
break;
case Artist::SortMethod::ByName:
query.orderBy("a.name COLLATE NOCASE");
break;
case Artist::SortMethod::BySortName:
query.orderBy("a.sort_name COLLATE NOCASE");
break;
}
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Artist::pointer>
Artist::getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
if (after)
query.where("t.file_last_write > ?").bind(*after);
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.orderBy("t.file_last_write DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Artist::pointer>
Artist::getStarred(Session& session,
User::pointer user,
const std::vector<ClusterId>& clusters,
std::optional<TrackArtistLinkType> linkType,
SortMethod sortMethod,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Artist>>(session, "SELECT DISTINCT a from artist a", clusters, {}, linkType)};
{
std::ostringstream oss;
oss << "a.id IN (SELECT DISTINCT a.id FROM artist a"
" INNER JOIN user_artist_starred uas ON uas.artist_id = a.id"
" INNER JOIN user u ON u.id = uas.user_id WHERE u.id = ?)";
query.bind(user->getId());
query.where(oss.str());
}
switch (sortMethod)
{
case Artist::SortMethod::None:
break;
case Artist::SortMethod::ByName:
query.orderBy("name COLLATE NOCASE");
break;
case Artist::SortMethod::BySortName:
query.orderBy("sort_name COLLATE NOCASE");
break;
}
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection = query
.groupBy("a.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1);
std::vector<pointer> res (collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return std::vector<pointer>(res.begin(), res.end());
auto query {createQuery(session, params)};
return execQuery(query, params.range);
}
std::vector<Release::pointer>
@@ -463,32 +271,19 @@ Artist::getTracks(std::optional<TrackArtistLinkType> linkType) const
return std::vector<Track::pointer>(tracks.begin(), tracks.end());
}
std::vector<Track::pointer>
Artist::getNonReleaseTracks(std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
RangeResults<Track::pointer>
Artist::getNonReleaseTracks(std::optional<TrackArtistLinkType> linkType, Range range) const
{
assert(session());
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(getId())
.where("t.release_id is NULL")
.orderBy("t.name")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)};
.orderBy("t.name")};
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {query.resultList()};
std::vector<Track::pointer> res(tracks.begin(), tracks.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
return execQuery(query, range);
}
bool
@@ -518,14 +313,14 @@ Artist::getRandomTracks(std::optional<std::size_t> count) const
return std::vector<Track::pointer>(tracks.begin(), tracks.end());
}
std::vector<Artist::pointer>
Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::optional<Range> range) const
RangeResults<ArtistId>
Artist::findSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, Range range) const
{
assert(session());
std::ostringstream oss;
oss <<
"SELECT a FROM artist a"
"SELECT a.id FROM artist a"
" INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN track t ON t.id = t_a_l.track_id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
@@ -554,19 +349,15 @@ Artist::getSimilarArtists(EnumSet<TrackArtistLinkType> artistLinkTypes, std::opt
oss << ")";
}
Wt::Dbo::Query<Wt::Dbo::ptr<Artist>> query {session()->query<Wt::Dbo::ptr<Artist>>(oss.str())
auto query {session()->query<ArtistId>(oss.str())
.bind(getId())
.bind(getId())
.groupBy("a.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(range ? static_cast<int>(range->limit) : -1)
.offset(range ? static_cast<int>(range->offset) : -1)};
.orderBy("COUNT(*) DESC, RANDOM()")};
for (TrackArtistLinkType type : artistLinkTypes)
query.bind(type);
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res {query.resultList()};
return std::vector<pointer>(res.begin(), res.end());
return execQuery(query, range);
}
std::vector<std::vector<Cluster::pointer>>
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "services/database/AuthToken.hpp"
#include <Wt/Dbo/WtSqlTraits.h>
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "StringViewTraits.hpp"
#include "IdTypeTraits.hpp"
namespace Database
{
AuthToken::AuthToken(std::string_view value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
: _value {value}
, _expiry {expiry}
, _user {getDboPtr(user)}
{
}
AuthToken::pointer
AuthToken::create(Session& session, std::string_view value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
{
session.checkUniqueLocked();
AuthToken::pointer res {session.getDboSession().add(std::make_unique<AuthToken>(value, expiry, user))};
session.getDboSession().flush();
return res;
}
void
AuthToken::removeExpiredTokens(Session& session, const Wt::WDateTime& now)
{
session.checkUniqueLocked();
session.getDboSession().execute("DELETE FROM auth_token WHERE expiry < ?").bind(now);
}
AuthToken::pointer
AuthToken::find(Session& session, std::string_view value)
{
session.checkSharedLocked();
return session.getDboSession().find<AuthToken>()
.where("value = ?").bind(value)
.resultValue();
}
}
+55 -49
View File
@@ -24,8 +24,9 @@
#include "services/database/ScanSettings.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "Traits.hpp"
#include "Utils.hpp"
namespace Database {
@@ -35,6 +36,14 @@ Cluster::Cluster(ObjectPtr<ClusterType> type, std::string_view name)
{
}
std::size_t
Cluster::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM cluster");
}
Cluster::pointer
Cluster::create(Session& session, ObjectPtr<ClusterType> type, std::string_view name)
{
@@ -46,25 +55,26 @@ Cluster::create(Session& session, ObjectPtr<ClusterType> type, std::string_view
return res;
}
std::vector<Cluster::pointer>
Cluster::getAll(Session& session)
RangeResults<ClusterId>
Cluster::find(Session& session, Range range)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<ClusterId>("SELECT id FROM cluster")};
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> res {session.getDboSession().find<Cluster>()};
return std::vector<Cluster::pointer>(res.begin(), res.end());
return execQuery(query, range);
}
std::vector<Cluster::pointer>
Cluster::getAllOrphans(Session& session)
RangeResults<ClusterId>
Cluster::findOrphans(Session& session, Range range)
{
session.checkSharedLocked();
auto res {session.getDboSession().query<Wt::Dbo::ptr<Cluster>>("SELECT DISTINCT c FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)").resultList()};
return std::vector<Cluster::pointer>(res.begin(), res.end());
auto query {session.getDboSession().query<ClusterId>("SELECT DISTINCT c.id FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)")};
return execQuery(query, range);
}
Cluster::pointer
Cluster::getById(Session& session, ClusterId id)
Cluster::find(Session& session, ClusterId id)
{
session.checkSharedLocked();
@@ -77,29 +87,15 @@ Cluster::addTrack(ObjectPtr<Track> track)
_tracks.insert(getDboPtr(track));
}
std::vector<Track::pointer>
Cluster::getTracks(std::optional<std::size_t> offset, std::optional<std::size_t> limit) const
RangeResults<TrackId>
Cluster::getTracks(Range range) const
{
assert(session());
auto res {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(getId())
.offset(offset ? static_cast<int>(*offset) : -1)
.limit(limit ? static_cast<int>(*limit) : -1)
.resultList()};
auto query {session()->query<TrackId>("SELECT t.id FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("c.id = ?").bind(getId())};
return std::vector<Track::pointer>(res.begin(), res.end());
}
std::vector<TrackId>
Cluster::getTrackIds() const
{
assert(session());
Wt::Dbo::collection<TrackId> res = session()->query<TrackId>("SELECT t_c.track_id FROM track_cluster t_c INNER JOIN cluster c ON c.id = t_c.cluster_id")
.where("c.id = ?").bind(getId());
return std::vector<TrackId>(res.begin(), res.end());
return execQuery(query, range);
}
std::size_t
@@ -117,33 +113,42 @@ ClusterType::ClusterType(std::string_view name)
{
}
std::vector<ClusterType::pointer>
ClusterType::getAllOrphans(Session& session)
std::size_t
ClusterType::getCount(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<Wt::Dbo::ptr<ClusterType>> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
"SELECT c_t from cluster_type c_t"
" LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id")
.where("c.id IS NULL");
return std::vector<pointer>(res.begin(), res.end());
return session.getDboSession().query<int>("SELECT COUNT(*) FROM cluster_type");
}
std::vector<ClusterType::pointer>
ClusterType::getAllUsed(Session& session)
RangeResults<ClusterTypeId>
ClusterType::findOrphans(Session& session, Range range)
{
session.checkSharedLocked();
Wt::Dbo::collection<Wt::Dbo::ptr<ClusterType>> res = session.getDboSession().query<Wt::Dbo::ptr<ClusterType>>(
"SELECT DISTINCT c_t from cluster_type c_t")
.join("cluster c ON c_t.id = c.cluster_type_id");
auto query {session.getDboSession().query<ClusterTypeId>(
"SELECT c_t.id from cluster_type c_t"
" LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id")
.where("c.id IS NULL")};
return std::vector<pointer>(res.begin(), res.end());
return execQuery(query, range);
}
RangeResults<ClusterTypeId>
ClusterType::findUsed(Session& session, Range range)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<ClusterTypeId>(
"SELECT DISTINCT c_t.id from cluster_type c_t")
.join("cluster c ON c_t.id = c.cluster_type_id")};
return execQuery(query, range);
}
ClusterType::pointer
ClusterType::getByName(Session& session, const std::string& name)
ClusterType::find(Session& session, const std::string& name)
{
session.checkSharedLocked();
@@ -151,20 +156,21 @@ ClusterType::getByName(Session& session, const std::string& name)
}
ClusterType::pointer
ClusterType::getById(Session& session, ClusterTypeId id)
ClusterType::find(Session& session, ClusterTypeId id)
{
session.checkSharedLocked();
return session.getDboSession().find<ClusterType>().where("id = ?").bind(id).resultValue();
}
std::vector<ClusterType::pointer>
ClusterType::getAll(Session& session)
RangeResults<ClusterTypeId>
ClusterType::find(Session& session, Range range)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<ClusterType>().resultList()};
return std::vector<pointer>(res.begin(), res.end());
auto query {session.getDboSession().query<ClusterTypeId>("SELECT id from cluster_type")};
return execQuery(query, range);
}
ClusterType::pointer
+285
View File
@@ -0,0 +1,285 @@
/*
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "services/database/Listen.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "services/database/User.hpp"
#include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
namespace
{
using namespace Database;
Wt::Dbo::Query<ArtistId>
createArtistsQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType)
{
auto query {session.query<ArtistId>("SELECT a.id from artist a")
.join("track t ON t.id = t_a_l.track_id")
.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id")
.join("listen l ON l.track_id = t.id")
.where("l.user_id = ?").bind(userId)
.where("l.scrobbler = ?").bind(scrobbler)};
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
if (!clusterIds.empty())
{
std::ostringstream oss;
oss << "a.id IN (SELECT DISTINCT a.id FROM artist a"
" INNER JOIN track t ON t.id = t_a_l.track_id"
" INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (auto id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(id);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
Wt::Dbo::Query<ReleaseId>
createReleasesQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector<ClusterId>& clusterIds)
{
auto query {session.query<ReleaseId>("SELECT r.id from release r")
.join("track t ON t.release_id = r.id")
.join("listen l ON l.track_id = t.id")
.where("l.user_id = ?").bind(userId)
.where("l.scrobbler = ?").bind(scrobbler)};
if (!clusterIds.empty())
{
std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (ClusterId id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(id);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
Wt::Dbo::Query<TrackId>
createTracksQuery(Wt::Dbo::Session& session, UserId userId, Scrobbler scrobbler, const std::vector<ClusterId>& clusterIds)
{
auto query {session.query<TrackId>("SELECT t.id from track t")
.join("listen l ON l.track_id = t.id")
.where("l.user_id = ?").bind(userId)
.where("l.scrobbler = ?").bind(scrobbler)};
if (!clusterIds.empty())
{
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (auto id : clusterIds)
{
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
query.bind(id);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")";
query.where(oss.str());
}
return query;
}
}
namespace Database
{
Listen::Listen(ObjectPtr<User> user, ObjectPtr<Track> track, Scrobbler scrobbler, const Wt::WDateTime& dateTime)
: _dateTime {Wt::WDateTime::fromTime_t(dateTime.toTime_t())}
, _scrobbler {scrobbler}
, _user {getDboPtr(user)}
, _track {getDboPtr(track)}
{}
std::size_t
Listen::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM listen");
}
Listen::pointer
Listen::find(Session& session, ListenId id)
{
session.checkSharedLocked();
return session.getDboSession().find<Listen>().where("id = ?").bind(id).resultValue();
}
RangeResults<Listen::pointer>
Listen::find(Session& session, UserId userId, Scrobbler scrobbler, Range range)
{
session.checkSharedLocked();
auto query {session.getDboSession().find<Listen>()
.where("user_id = ?").bind(userId)
.where("scrobbler = ?").bind(scrobbler)
.orderBy("date_time")};
return execQuery(query, range);
}
Listen::pointer
Listen::find(Session& session, UserId userId, TrackId trackId, Scrobbler scrobbler, const Wt::WDateTime& dateTime)
{
session.checkSharedLocked();
return session.getDboSession().find<Listen>()
.where("user_id = ?").bind(userId)
.where("track_id = ?").bind(trackId)
.where("scrobbler = ?").bind(scrobbler)
.where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t()))
.resultValue();
}
Listen::pointer
Listen::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track, Scrobbler scrobbler, const Wt::WDateTime& dateTime)
{
session.checkUniqueLocked();
Listen::pointer res {session.getDboSession().add(std::make_unique<Listen>(user, track, scrobbler, dateTime))};
session.getDboSession().flush();
return res;
}
RangeResults<ArtistId>
Listen::getTopArtists(Session& session,
UserId userId,
Scrobbler scrobbler,
const std::vector<ClusterId>& clusterIds,
std::optional<TrackArtistLinkType> linkType,
Range range)
{
auto query {createArtistsQuery(session.getDboSession(), userId, scrobbler, clusterIds, linkType)};
auto collection {query
.orderBy("COUNT(a.id) DESC")
.groupBy("a.id")};
return execQuery(query, range);
}
RangeResults<ReleaseId>
Listen::getTopReleases(Session& session,
UserId userId,
Scrobbler scrobbler,
const std::vector<ClusterId>& clusterIds,
Range range)
{
auto query {createReleasesQuery(session.getDboSession(), userId, scrobbler, clusterIds)
.orderBy("COUNT(r.id) DESC")
.groupBy("r.id")};
return execQuery(query, range);
}
RangeResults<TrackId>
Listen::getTopTracks(Session& session,
UserId userId,
Scrobbler scrobbler,
const std::vector<ClusterId>& clusterIds,
Range range)
{
auto query {createTracksQuery(session.getDboSession(), userId, scrobbler, clusterIds)
.orderBy("COUNT(t.id) DESC")
.groupBy("t.id")};
return execQuery(query, range);
}
RangeResults<ArtistId>
Listen::getRecentArtists(Session& session,
UserId userId,
Scrobbler scrobbler,
const std::vector<ClusterId>& clusterIds,
std::optional<TrackArtistLinkType> linkType,
Range range)
{
auto query {createArtistsQuery(session.getDboSession(), userId, scrobbler, clusterIds, linkType)
.groupBy("a.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC")};
return execQuery(query, range);
}
RangeResults<ReleaseId>
Listen::getRecentReleases(Session& session,
UserId userId,
Scrobbler scrobbler,
const std::vector<ClusterId>& clusterIds,
Range range)
{
auto query {createReleasesQuery(session.getDboSession(), userId, scrobbler, clusterIds)
.groupBy("r.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC")};
return execQuery(query, range);
}
RangeResults<TrackId>
Listen::getRecentTracks(Session& session,
UserId userId,
Scrobbler scrobbler,
const std::vector<ClusterId>& clusterIds,
Range range)
{
auto query {createTracksQuery(session.getDboSession(), userId, scrobbler, clusterIds)
.groupBy("t.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC")};
return execQuery(query, range);
}
} // namespace Database
+72 -219
View File
@@ -28,28 +28,45 @@
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
#include "Traits.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
namespace Database
{
template <typename T>
static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords)
Wt::Dbo::Query<ReleaseId>
createQuery(Session& session, const Release::FindParameters& params)
{
auto query {session.getDboSession().query<ReleaseId>("SELECT DISTINCT r.id from release r")};
auto query {session.getDboSession().query<T>(queryStr)};
query.join("track t ON t.release_id = r.id");
if (params.sortMethod == ReleaseSortMethod::LastWritten
|| params.writtenAfter.isValid()
|| params.dateRange)
{
query.join("track t ON t.release_id = r.id");
}
for (std::string_view keyword : keywords)
if (params.writtenAfter.isValid())
query.where("t.file_last_write > ?").bind(params.writtenAfter);
if (params.dateRange)
{
query.where("t.date >= ?").bind(params.dateRange->begin);
query.where("t.date <= ?").bind(params.dateRange->end);
}
for (std::string_view keyword : params.keywords)
query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + escapeLikeKeyword(keyword) + "%");
if (!clusterIds.empty())
if (params.starringUser.isValid())
{
assert(params.scrobbler);
query.join("starred_release s_r ON s_r.release_id = r.id")
.where("s_r.user_id = ?").bind(params.starringUser)
.where("s_r.scrobbler = ?").bind(*params.scrobbler);
}
if (!params.clusters.empty())
{
std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
@@ -58,18 +75,40 @@ createQuery(Session& session,
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause;
for (const ClusterId clusterId : clusterIds)
for (const ClusterId clusterId : params.clusters)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")";
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
query.where(oss.str());
}
switch (params.sortMethod)
{
case ReleaseSortMethod::None:
break;
case ReleaseSortMethod::Name:
query.orderBy("r.name COLLATE NOCASE");
break;
case ReleaseSortMethod::Random:
query.orderBy("RANDOM()");
break;
case ReleaseSortMethod::LastWritten:
query.orderBy("t.file_last_write DESC");
break;
case ReleaseSortMethod::Date:
query.orderBy("t.date, r.name COLLATE NOCASE");
break;
case ReleaseSortMethod::StarredDateDesc:
assert(params.starringUser.isValid());
query.orderBy("s_r.date_time DESC");
break;
}
return query;
}
@@ -80,7 +119,7 @@ _MBID {MBID ? MBID->getAsString() : ""}
}
std::vector<Release::pointer>
Release::getByName(Session& session, const std::string& name)
Release::find(Session& session, const std::string& name)
{
session.checkUniqueLocked();
@@ -93,7 +132,7 @@ Release::getByName(Session& session, const std::string& name)
}
Release::pointer
Release::getByMBID(Session& session, const UUID& mbid)
Release::find(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
@@ -104,7 +143,7 @@ Release::getByMBID(Session& session, const UUID& mbid)
}
Release::pointer
Release::getById(Session& session, ReleaseId id)
Release::find(Session& session, ReleaseId id)
{
session.checkSharedLocked();
@@ -137,230 +176,44 @@ Release::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<Release>().resultList().size();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM release");
}
std::vector<Release::pointer>
Release::getAll(Session& session, std::optional<Range> range)
RangeResults<ReleaseId>
Release::findOrderedByArtist(Session& session, Range range)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<Release>()
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) : -1)
.orderBy("name COLLATE NOCASE")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<ReleaseId>
Release::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<ReleaseId> res = session.getDboSession().query<ReleaseId>("SELECT id FROM release");
return std::vector<ReleaseId>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getAllOrderedByArtist(Session& session, std::optional<std::size_t> offset, std::optional<std::size_t> size)
{
session.checkSharedLocked();
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>(
"SELECT DISTINCT r FROM release r"
// TODO merge with execQuery
auto query {session.getDboSession().query<ReleaseId>(
"SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON r.id = t.release_id"
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id"
" INNER JOIN artist a ON t_a_l.artist_id = a.id")
.offset(offset ? static_cast<int>(*offset) : -1)
.limit(size ? static_cast<int>(*size) : -1)
.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE")
.resultList()};
.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE")};
return std::vector<pointer>(res.begin(), res.end());
return execQuery(query, range);
}
std::vector<Release::pointer>
Release::getAllRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> size)
RangeResults<ReleaseId>
Release::findOrphans(Session& session, Range range)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT DISTINCT r from release r", clusterIds, {})};
auto res {query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
auto query {session.getDboSession().query<ReleaseId>("select r.id from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL")};
return execQuery(query, range);
}
std::vector<ReleaseId>
Release::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> size)
RangeResults<ReleaseId>
Release::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
auto query {createQuery<ReleaseId>(session, "SELECT DISTINCT r.id from release r", clusterIds, {})};
auto query {createQuery(session, params)};
Wt::Dbo::collection<ReleaseId> res = query
.orderBy("RANDOM()")
.limit(size ? static_cast<int>(*size) : -1);
return std::vector<ReleaseId>(res.begin(), res.end());
return execQuery(query, params.range);
}
std::vector<Release::pointer>
Release::getAllOrphans(Session& session)
{
session.checkSharedLocked();
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL").resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getLastWritten(Session& session,
std::optional<Wt::WDateTime> after,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, {})};
if (after)
query.where("t.file_last_write > ?").bind(after);
auto collection {query
.orderBy("t.file_last_write DESC")
.groupBy("r.id")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Release::pointer>
Release::getByYear(Session& session, int yearFrom, int yearTo, std::optional<Range> range)
{
auto res {session.getDboSession().query<Wt::Dbo::ptr<Release>>
("SELECT DISTINCT r from release r INNER JOIN track t ON r.id = t.release_id")
.where("t.date >= ?").bind(Wt::WDate {yearFrom, 1, 1})
.where("t.date <= ?").bind(Wt::WDate {yearTo, 12, 31})
.orderBy("t.date, r.name COLLATE NOCASE")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) : -1)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
Release::getStarred(Session& session,
User::pointer user,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, {})};
{
std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN user_release_starred urs ON urs.release_id = r.id"
" INNER JOIN user u ON u.id = urs.user_id WHERE u.id = ?)";
query.bind(user->getId());
query.where(oss.str());
}
auto collection {query
.groupBy("r.id")
.orderBy("r.name COLLATE NOCASE")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Release::pointer>
Release::getByClusters(Session& session, const std::vector<ClusterId>& clusters)
{
assert(!clusters.empty());
session.checkSharedLocked();
bool moreResults;
return getByFilter(session, clusters, {}, std::nullopt, moreResults);
}
std::vector<Release::pointer>
Release::getByFilter(Session& session,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<Range> range,
bool& moreResults)
{
session.checkSharedLocked();
auto collection {createQuery<Wt::Dbo::ptr<Release>>(session, "SELECT r from release r", clusterIds, keywords)
.groupBy("r.id")
.orderBy("r.name COLLATE NOCASE")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<ReleaseId>
Release::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<ReleaseId> res = session.getDboSession().query<ReleaseId>
("SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<ReleaseId>(res.begin(), res.end());
}
std::optional<std::size_t>
Release::getTotalTrack(void) const
{
@@ -111,7 +111,7 @@ ScanSettings::setClusterTypes(Session& session, const std::set<std::string>& clu
// Create any missing cluster type
for (const std::string& clusterTypeName : clusterTypeNames)
{
ClusterType::pointer clusterType {ClusterType::getByName(session, clusterTypeName)};
ClusterType::pointer clusterType {ClusterType::find(session, clusterTypeName)};
if (!clusterType)
{
LMS_LOG(DB, INFO) << "Creating cluster type " << clusterTypeName;
+70 -1
View File
@@ -28,10 +28,15 @@
#include "utils/Logger.hpp"
#include "services/database/Artist.hpp"
#include "services/database/AuthToken.hpp"
#include "services/database/Cluster.hpp"
#include "services/database/Db.hpp"
#include "services/database/Listen.hpp"
#include "services/database/Release.hpp"
#include "services/database/ScanSettings.hpp"
#include "services/database/StarredArtist.hpp"
#include "services/database/StarredRelease.hpp"
#include "services/database/StarredTrack.hpp"
#include "services/database/Track.hpp"
#include "services/database/TrackBookmark.hpp"
#include "services/database/TrackArtistLink.hpp"
@@ -43,7 +48,7 @@ namespace Database
{
using Version = std::size_t;
static constexpr Version LMS_DATABASE_VERSION {31};
static constexpr Version LMS_DATABASE_VERSION {32};
class VersionInfo
{
@@ -360,6 +365,61 @@ CREATE TABLE "track_backup" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion();
}
else if (version == 31)
{
// new star system, using dedicated ObjectSets per scrobbler
_session.execute("DROP TABLE user_artist_starred");
_session.execute("DROP TABLE user_release_starred");
_session.execute("DROP TABLE user_track_starred");
_session.execute(R"(
CREATE TABLE IF NOT EXISTS "starred_artist" (
"id" integer primary key autoincrement,
"version" integer not null,
"scrobbler" integer not null,
"date_time" text,
"artist_id" bigint,
"user_id" bigint,
constraint "fk_starred_artist_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred,
constraint "fk_starred_artist_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
))");
_session.execute(R"(
CREATE TABLE IF NOT EXISTS "starred_release" (
"id" integer primary key autoincrement,
"version" integer not null,
"scrobbler" integer not null,
"date_time" text,
"release_id" bigint,
"user_id" bigint,
constraint "fk_starred_release_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred,
constraint "fk_starred_release_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
))");
_session.execute(R"(
CREATE TABLE IF NOT EXISTS "starred_track" (
"id" integer primary key autoincrement,
"version" integer not null,
"scrobbler" integer not null,
"date_time" text,
"track_id" bigint,
"user_id" bigint,
constraint "fk_starred_track_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
constraint "fk_starred_track_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
))");
_session.execute(R"(
CREATE TABLE IF NOT EXISTS "listen" (
"id" integer primary key autoincrement,
"version" integer not null,
"date_time" text,
"scrobbler" integer not null,
"track_id" bigint,
"user_id" bigint,
constraint "fk_listen_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
constraint "fk_listen_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
))");
}
else
{
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
@@ -380,8 +440,12 @@ Session::Session(Db& db)
_session.mapClass<AuthToken>("auth_token");
_session.mapClass<Cluster>("cluster");
_session.mapClass<ClusterType>("cluster_type");
_session.mapClass<Listen>("listen");
_session.mapClass<Release>("release");
_session.mapClass<ScanSettings>("scan_settings");
_session.mapClass<StarredArtist>("starred_artist");
_session.mapClass<StarredRelease>("starred_release");
_session.mapClass<StarredTrack>("starred_track");
_session.mapClass<Track>("track");
_session.mapClass<TrackBookmark>("track_bookmark");
_session.mapClass<TrackArtistLink>("track_artist_link");
@@ -484,6 +548,11 @@ Session::prepareTables()
_session.execute("CREATE INDEX IF NOT EXISTS track_artist_link_type_idx ON track_artist_link(type)");
_session.execute("CREATE INDEX IF NOT EXISTS track_bookmark_user_idx ON track_bookmark(user_id)");
_session.execute("CREATE INDEX IF NOT EXISTS track_bookmark_user_track_idx ON track_bookmark(user_id,track_id)");
_session.execute("CREATE INDEX IF NOT EXISTS listen_scrobbler_idx ON listen(scrobbler)");
_session.execute("CREATE INDEX IF NOT EXISTS listen_user_scrobbler_idx ON listen(user_id,scrobbler)");
_session.execute("CREATE INDEX IF NOT EXISTS starred_artist_user_scrobbler_idx ON starred_artist(user_id,scrobbler)");
_session.execute("CREATE INDEX IF NOT EXISTS starred_release_user_scrobbler_idx ON starred_release(user_id,scrobbler)");
_session.execute("CREATE INDEX IF NOT EXISTS starred_track_user_scrobbler_idx ON starred_track(user_id,scrobbler)");
}
// Initial settings tables
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "services/database/StarredArtist.hpp"
#include <Wt/Dbo/WtSqlTraits.h>
#include "services/database/Artist.hpp"
#include "services/database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
namespace Database
{
StarredArtist::StarredArtist(ObjectPtr<Artist> artist, ObjectPtr<User> user, Scrobbler scrobbler)
: _scrobbler {scrobbler}
, _artist {getDboPtr(artist)}
, _user {getDboPtr(user)}
{
}
std::size_t
StarredArtist::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM starred_artist");
}
StarredArtist::pointer
StarredArtist::find(Session& session, StarredArtistId id)
{
session.checkSharedLocked();
return session.getDboSession().find<StarredArtist>().where("id = ?").bind(id).resultValue();
}
StarredArtist::pointer
StarredArtist::find(Session& session, ArtistId artistId, UserId userId, Scrobbler scrobbler)
{
session.checkSharedLocked();
return session.getDboSession().find<StarredArtist>()
.where("artist_id = ?").bind(artistId)
.where("user_id = ?").bind(userId)
.where("scrobbler = ?").bind(scrobbler)
.resultValue();
}
StarredArtist::pointer
StarredArtist::create(Session& session, ObjectPtr<Artist> artist, ObjectPtr<User> user, Scrobbler scrobbler)
{
session.checkUniqueLocked();
StarredArtist::pointer res {session.getDboSession().add(std::make_unique<StarredArtist>(artist, user, scrobbler))};
session.getDboSession().flush();
return res;
}
void
StarredArtist::setDateTime(const Wt::WDateTime& dateTime)
{
_dateTime = normalizeDateTime(dateTime);
}
}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "services/database/StarredRelease.hpp"
#include <Wt/Dbo/WtSqlTraits.h>
#include "services/database/Release.hpp"
#include "services/database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
namespace Database
{
StarredRelease::StarredRelease(ObjectPtr<Release> release, ObjectPtr<User> user, Scrobbler scrobbler)
: _scrobbler {scrobbler}
, _release {getDboPtr(release)}
, _user {getDboPtr(user)}
{
}
std::size_t
StarredRelease::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM starred_release");
}
StarredRelease::pointer
StarredRelease::find(Session& session, StarredReleaseId id)
{
session.checkSharedLocked();
return session.getDboSession().find<StarredRelease>().where("id = ?").bind(id).resultValue();
}
StarredRelease::pointer
StarredRelease::find(Session& session, ReleaseId releaseId, UserId userId, Scrobbler scrobbler)
{
session.checkSharedLocked();
return session.getDboSession().find<StarredRelease>()
.where("release_id = ?").bind(releaseId)
.where("user_id = ?").bind(userId)
.where("scrobbler = ?").bind(scrobbler)
.resultValue();
}
StarredRelease::pointer
StarredRelease::create(Session& session, ObjectPtr<Release> release, ObjectPtr<User> user, Scrobbler scrobbler)
{
session.checkUniqueLocked();
StarredRelease::pointer res {session.getDboSession().add(std::make_unique<StarredRelease>(release, user, scrobbler))};
session.getDboSession().flush();
return res;
}
void
StarredRelease::setDateTime(const Wt::WDateTime& dateTime)
{
_dateTime = normalizeDateTime(dateTime);
}
}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2021 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "services/database/StarredTrack.hpp"
#include <Wt/Dbo/WtSqlTraits.h>
#include "services/database/Track.hpp"
#include "services/database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
namespace Database
{
StarredTrack::StarredTrack(ObjectPtr<Track> track, ObjectPtr<User> user, Scrobbler scrobbler)
: _scrobbler {scrobbler}
, _track {getDboPtr(track)}
, _user {getDboPtr(user)}
{
}
std::size_t
StarredTrack::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM starred_track");
}
StarredTrack::pointer
StarredTrack::find(Session& session, StarredTrackId id)
{
session.checkSharedLocked();
return session.getDboSession().find<StarredTrack>().where("id = ?").bind(id).resultValue();
}
StarredTrack::pointer
StarredTrack::find(Session& session, TrackId trackId, UserId userId, Scrobbler scrobbler)
{
session.checkSharedLocked();
return session.getDboSession().find<StarredTrack>()
.where("track_id = ?").bind(trackId)
.where("user_id = ?").bind(userId)
.where("scrobbler = ?").bind(scrobbler)
.resultValue();
}
StarredTrack::pointer
StarredTrack::create(Session& session, ObjectPtr<Track> track, ObjectPtr<User> user, Scrobbler scrobbler)
{
session.checkUniqueLocked();
StarredTrack::pointer res {session.getDboSession().add(std::make_unique<StarredTrack>(track, user, scrobbler))};
session.getDboSession().flush();
return res;
}
void
StarredTrack::setDateTime(const Wt::WDateTime& dateTime)
{
_dateTime = normalizeDateTime(dateTime);
}
}
+93 -262
View File
@@ -30,29 +30,36 @@
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
#include "Utils.hpp"
namespace Database {
template <typename T>
static
Wt::Dbo::Query<T>
createQuery(Session& session,
const std::string& queryStr,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords)
Wt::Dbo::Query<TrackId>
createQuery(Session& session, const Track::FindParameters& params)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<T>(queryStr)};
auto query {session.getDboSession().query<TrackId>("SELECT t.id from track t")};
for (std::string_view keyword : keywords)
for (std::string_view keyword : params.keywords)
query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + escapeLikeKeyword(keyword) + "%");
if (!clusterIds.empty())
if (params.writtenAfter.isValid())
query.where("t.file_last_write > ?").bind(params.writtenAfter);
if (params.starringUser.isValid())
{
assert(params.scrobbler);
query.join("starred_track s_t ON s_t.track_id = t.id")
.where("s_t.user_id = ?").bind(params.starringUser)
.where("s_t.scrobbler = ?").bind(*params.scrobbler);
}
if (!params.clusters.empty())
{
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
@@ -60,18 +67,34 @@ createQuery(Session& session,
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause;
for (const ClusterId clusterId : clusterIds)
for (const ClusterId clusterId : params.clusters)
{
clusterClause.Or(WhereClause("c.id = ?"));
query.bind(clusterId);
}
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")";
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
query.where(oss.str());
}
switch (params.sortMethod)
{
case TrackSortMethod::None:
break;
case TrackSortMethod::LastWritten:
query.orderBy("t.file_last_write DESC");
break;
case TrackSortMethod::Random:
query.orderBy("RANDOM()");
break;
case TrackSortMethod::StarredDateDesc:
assert(params.starringUser.isValid());
query.orderBy("s_t.date_time DESC");
break;
}
return query;
}
@@ -80,6 +103,17 @@ Track::Track(const std::filesystem::path& p)
{
}
Track::pointer
Track::create(Session& session, const std::filesystem::path& p)
{
session.checkUniqueLocked();
Track::pointer res {session.getDboSession().add(std::make_unique<Track>(p))};
session.getDboSession().flush();
return res;
}
std::size_t
Track::getCount(Session& session)
{
@@ -88,58 +122,8 @@ Track::getCount(Session& session)
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track");
}
std::vector<Track::pointer>
Track::getAll(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<Track>()
.limit(limit ? static_cast<int>(*limit) : -1)
.resultList()};
return std::vector<Track::pointer>(std::cbegin(res), std::cend(res));
}
std::vector<Track::pointer>
Track::getAllRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
auto collection {query
.orderBy("RANDOM()")
.limit(limit ? static_cast<int>(*limit) + 1: -1)
.resultList()};
return std::vector<pointer>(collection.begin(), collection.end());
}
std::vector<TrackId>
Track::getAllIdsRandom(Session& session, const std::vector<ClusterId>& clusterIds, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
auto query {createQuery<TrackId>(session, "SELECT t.id from track t", clusterIds, {})};
Wt::Dbo::collection<TrackId> collection = query
.orderBy("RANDOM()")
.limit(limit ? static_cast<int>(*limit) + 1: -1);
return std::vector<TrackId>(collection.begin(), collection.end());
}
std::vector<TrackId>
Track::getAllIds(Session& session)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>("SELECT id FROM track");
return std::vector<TrackId>(res.begin(), res.end());
}
Track::pointer
Track::getByPath(Session& session, const std::filesystem::path& p)
Track::findByPath(Session& session, const std::filesystem::path& p)
{
session.checkSharedLocked();
@@ -147,7 +131,7 @@ Track::getByPath(Session& session, const std::filesystem::path& p)
}
Track::pointer
Track::getById(Session& session, TrackId id)
Track::find(Session& session, TrackId id)
{
session.checkSharedLocked();
@@ -165,7 +149,7 @@ Track::exists(Session& session, TrackId id)
}
std::vector<Track::pointer>
Track::getByRecordingMBID(Session& session, const UUID& mbid)
Track::findByRecordingMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
@@ -176,151 +160,52 @@ Track::getByRecordingMBID(Session& session, const UUID& mbid)
return std::vector<Track::pointer>(res.begin(), res.end());
}
Track::pointer
Track::create(Session& session, const std::filesystem::path& p)
{
session.checkUniqueLocked();
Track::pointer res {session.getDboSession().add(std::make_unique<Track>(p))};
session.getDboSession().flush();
return res;
}
std::vector<std::pair<TrackId, std::filesystem::path>>
Track::getAllPaths(Session& session, std::optional<std::size_t> offset, std::optional<std::size_t> size)
RangeResults<Track::PathResult>
Track::findPaths(Session& session, Range range)
{
using QueryResultType = std::tuple<TrackId, std::string>;
session.checkSharedLocked();
Wt::Dbo::collection<QueryResultType> queryRes = session.getDboSession().query<QueryResultType>("SELECT id,file_path FROM track")
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
// TODO Dbo traits on filesystem
auto query {session.getDboSession().query<QueryResultType>("SELECT id, file_path FROM track")};
std::vector<std::pair<TrackId, std::filesystem::path>> result;
result.reserve(queryRes.size());
RangeResults<QueryResultType> queryResults {execQuery(query, range)};
std::transform(std::begin(queryRes), std::end(queryRes), std::back_inserter(result),
RangeResults<PathResult> res;
res.range = queryResults.range;
res.moreResults = queryResults.moreResults;
res.results.reserve(queryResults.results.size());
std::transform(std::cbegin(queryResults.results), std::cend(queryResults.results), std::back_inserter(res.results),
[](const QueryResultType& queryResult)
{
return std::make_pair(std::get<0>(queryResult), std::get<1>(queryResult));
return PathResult {std::get<0>(queryResult), std::get<1>(queryResult)};
});
return result;
}
std::vector<Track::pointer>
Track::getMBIDDuplicates(Session& session)
{
session.checkSharedLocked();
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>( "SELECT track FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)")
.orderBy("track.release_id,track.disc_number,track.track_number,track.mbid")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getLastWritten(Session& session, std::optional<Wt::WDateTime> after, const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
if (after)
query.where("t.file_last_write > ?").bind(after);
auto collection {query
.orderBy("t.file_last_write DESC")
.groupBy("t.id")
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Track::pointer>
Track::getAllWithRecordingMBIDAndMissingFeatures(Session& session)
RangeResults<TrackId>
Track::findMBIDDuplicates(Session& session, Range range)
{
session.checkSharedLocked();
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>
("SELECT t FROM track t")
auto query {session.getDboSession().query<TrackId>( "SELECT track.id 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 execQuery(query, range);
}
RangeResults<TrackId>
Track::findWithRecordingMBIDAndMissingFeatures(Session& session, Range range)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<TrackId>("SELECT t.id FROM track t")
.where("LENGTH(t.recording_mbid) > 0")
.where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)")
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
.where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)")};
std::vector<TrackId>
Track::getAllIdsWithFeatures(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>
("SELECT t.id FROM track t")
.where("EXISTS (SELECT * from track_features t_f WHERE t_f.track_id = t.id)")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<TrackId>(res.begin(), res.end());
}
std::vector<TrackId>
Track::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
{
session.checkSharedLocked();
Wt::Dbo::collection<TrackId> res = session.getDboSession().query<TrackId>
("SELECT DISTINCT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.limit(limit ? static_cast<int>(*limit) : -1);
return std::vector<TrackId>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getStarred(Session& session,
ObjectPtr<User> user,
const std::vector<ClusterId>& clusterIds,
std::optional<Range> range, bool& moreResults)
{
session.checkSharedLocked();
auto query {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, {})};
{
std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
" INNER JOIN user_track_starred uts ON uts.track_id = t.id"
" INNER JOIN user u ON u.id = uts.user_id WHERE u.id = ?)";
query.bind(user->getId().toString());
query.where(oss.str());
}
auto collection {query
.offset(range ? static_cast<int>(range->offset) : -1)
.limit(range ? static_cast<int>(range->limit) + 1: -1)
.resultList()};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
return execQuery(query, range);
}
std::vector<Cluster::pointer>
@@ -342,56 +227,31 @@ Track::getClusterIds() const
return std::vector<ClusterId>(res.begin(), res.end());
}
bool
Track::hasTrackFeatures() const
{
return (_trackFeatures.lock() != Wt::Dbo::ptr<Database::TrackFeatures> {});
}
std::vector<Track::pointer>
Track::getByFilter(Session& session,
const std::vector<ClusterId>& clusterIds,
const std::vector<std::string_view>& keywords,
std::optional<Range> range,
bool& moreResults)
RangeResults<TrackId>
Track::find(Session& session, const FindParameters& parameters)
{
session.checkSharedLocked();
auto collection {createQuery<Wt::Dbo::ptr<Track>>(session, "SELECT t from track t", clusterIds, keywords)
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto query {createQuery(session, parameters)};
std::vector<pointer> res(collection.begin(), collection.end());
if (range && (res.size() == static_cast<std::size_t>(range->limit) + 1))
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
return execQuery(query, parameters.range);
}
std::vector<Track::pointer>
Track::getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName)
RangeResults<TrackId>
Track::findByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName)
{
session.checkSharedLocked();
auto res {session.getDboSession().query<Wt::Dbo::ptr<Track>>("SELECT t from track t")
auto query {session.getDboSession().query<TrackId>("SELECT t.id from track t")
.join("release r ON t.release_id = r.id")
.where("t.name = ?").bind(trackName)
.where("r.name = ?").bind(releaseName)
.resultList()};
return std::vector<pointer>(res.begin(), res.end());
.where("r.name = ?").bind(releaseName)};
return execQuery(query, Range {});
}
std::vector<Track::pointer>
Track::getSimilarTracks(Session& session,
const std::vector<TrackId>& tracks,
std::optional<std::size_t> offset,
std::optional<std::size_t> size)
RangeResults<TrackId>
Track::findSimilarTracks(Session& session, const std::vector<TrackId>& tracks, Range range)
{
assert(!tracks.empty());
session.checkSharedLocked();
@@ -404,15 +264,13 @@ Track::getSimilarTracks(Session& session,
oss << "?";
}
auto query {session.getDboSession().query<Wt::Dbo::ptr<Track>>(
"SELECT t FROM track t"
auto query {session.getDboSession().query<TrackId>(
"SELECT t.id FROM track t"
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
" AND t_c.cluster_id IN (SELECT c.id FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id WHERE t_c.track_id IN (" + oss.str() + "))"
" AND t.id NOT IN (" + oss.str() + ")")
.groupBy("t.id")
.orderBy("COUNT(*) DESC, RANDOM()")
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1)};
.orderBy("COUNT(*) DESC, RANDOM()")};
for (TrackId trackId : tracks)
query.bind(trackId);
@@ -420,22 +278,7 @@ Track::getSimilarTracks(Session& session,
for (TrackId trackId : tracks)
query.bind(trackId);
auto res {query.resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
Track::getByClusters(Session& session, const std::vector<ClusterId>& clusters)
{
assert(!clusters.empty());
session.checkSharedLocked();
bool moreResults;
return getByFilter(session,
clusters,
{}, // keywords
std::nullopt, // range
moreResults);
return execQuery(query, range);
}
void
@@ -458,12 +301,6 @@ Track::setClusters(const std::vector<ObjectPtr<Cluster>>& clusters)
_clusters.insert(getDboPtr(cluster));
}
void
Track::setFeatures(const ObjectPtr<TrackFeatures>& features)
{
_trackFeatures = getDboPtr(features);
}
std::optional<std::size_t>
Track::getTrackNumber() const
{
@@ -593,12 +430,6 @@ Track::getArtistLinks() const
return std::vector<TrackArtistLink::pointer>(_trackArtistLinks.begin(), _trackArtistLinks.end());
}
ObjectPtr<TrackFeatures>
Track::getTrackFeatures() const
{
return _trackFeatures.lock();
}
std::vector<std::vector<Cluster::pointer>>
Track::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
{
@@ -23,7 +23,7 @@
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "Traits.hpp"
#include "IdTypeTraits.hpp"
namespace Database {
@@ -46,7 +46,7 @@ TrackArtistLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<Arti
}
EnumSet<TrackArtistLinkType>
TrackArtistLink::getUsedTypes(Session& session)
TrackArtistLink::findUsedTypes(Session& session)
{
session.checkSharedLocked();
@@ -22,7 +22,8 @@
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "services/database/User.hpp"
#include "Traits.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
namespace Database {
@@ -32,6 +33,15 @@ _track {getDboPtr(track)}
{
}
std::size_t
TrackBookmark::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track_bookmark");
}
TrackBookmark::pointer
TrackBookmark::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track)
{
@@ -43,40 +53,30 @@ TrackBookmark::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> t
return res;
}
std::vector<TrackBookmark::pointer>
TrackBookmark::getAll(Session& session)
RangeResults<TrackBookmarkId>
TrackBookmark::find(Session& session, UserId userId, Range range)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<TrackBookmark>().resultList()};
return std::vector<TrackBookmark::pointer>(std::cbegin(res), std::cend(res));
}
auto query {session.getDboSession().query<TrackBookmarkId>("SELECT id from track_bookmark")
.where("user_id = ?").bind(userId)};
std::vector<TrackBookmark::pointer>
TrackBookmark::getByUser(Session& session, User::pointer user)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(user->getId())
.resultList()};
return std::vector<TrackBookmark::pointer>(std::cbegin(res), std::cend(res));
return execQuery(query, range);
}
TrackBookmark::pointer
TrackBookmark::getByUser(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track)
TrackBookmark::find(Session& session, UserId userId, TrackId trackId)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(user->getId())
.where("track_id = ?").bind(track->getId())
.where("user_id = ?").bind(userId)
.where("track_id = ?").bind(trackId)
.resultValue();
}
TrackBookmark::pointer
TrackBookmark::getById(Session& session, TrackBookmarkId id)
TrackBookmark::find(Session& session, TrackBookmarkId id)
{
session.checkSharedLocked();
@@ -85,6 +85,5 @@ TrackBookmark::getById(Session& session, TrackBookmarkId id)
.resultValue();
}
} // namespace Database
@@ -25,6 +25,8 @@
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "utils/Logger.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
namespace Database {
@@ -34,11 +36,53 @@ _track {getDboPtr(track)}
{
}
std::size_t
TrackFeatures::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track_features");
}
TrackFeatures::pointer
TrackFeatures::find(Session& session, TrackFeaturesId id)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackFeatures>()
.where("id = ?").bind(id)
.resultValue();
}
TrackFeatures::pointer
TrackFeatures::find(Session& session, TrackId trackId)
{
session.checkSharedLocked();
return session.getDboSession().find<TrackFeatures>()
.where("track_id = ?").bind(trackId)
.resultValue();
}
RangeResults<TrackFeaturesId>
TrackFeatures::find(Session& session, Range range)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<TrackFeaturesId>("SELECT id from track_features")};
return execQuery(query, range);
}
TrackFeatures::pointer
TrackFeatures::create(Session& session, ObjectPtr<Track> track, const std::string& jsonEncodedFeatures)
{
session.checkUniqueLocked();
return session.getDboSession().add(std::make_unique<TrackFeatures>(track, jsonEncodedFeatures));
TrackFeatures::pointer res {session.getDboSession().add(std::make_unique<TrackFeatures>(track, jsonEncodedFeatures))};
session.getDboSession().flush();
return res;
}
FeatureValues
@@ -51,6 +95,8 @@ TrackFeatures::getFeatureValues(const FeatureName& featureNode) const
FeatureValuesMap
TrackFeatures::getFeatureValuesMap(const std::unordered_set<FeatureName>& featureNames) const
{
FeatureValuesMap res;
try
{
std::istringstream iss {_data};
@@ -58,7 +104,6 @@ TrackFeatures::getFeatureValuesMap(const std::unordered_set<FeatureName>& featur
boost::property_tree::read_json(iss, root);
FeatureValuesMap res;
for (const FeatureName& featureName : featureNames)
{
FeatureValues& featureValues {res[featureName]};
@@ -75,14 +120,14 @@ TrackFeatures::getFeatureValuesMap(const std::unordered_set<FeatureName>& featur
if (!hasChildren)
featureValues.push_back(node.get_value<double>());
}
return res;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(DB, ERROR) << "Track " << _track.id() << ": ptree exception: " << error.what();
return {};
res.clear();
}
return res;
}
} // namespace Database
+139 -51
View File
@@ -30,7 +30,8 @@
#include "services/database/Track.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
namespace Database {
@@ -55,56 +56,54 @@ TrackList::create(Session& session, std::string_view name, Type type, bool isPub
return res;
}
TrackList::pointer
TrackList::get(Session& session, std::string_view name, Type type, ObjectPtr<User> user)
std::size_t
TrackList::getCount(Session& session)
{
session.checkSharedLocked();
assert(user);
return session.getDboSession().query<int>("SELECT COUNT(*) FROM tracklist");
}
TrackList::pointer
TrackList::find(Session& session, std::string_view name, Type type, UserId userId)
{
session.checkSharedLocked();
assert(userId.isValid());
return session.getDboSession().find<TrackList>()
.where("name = ?").bind(name)
.where("type = ?").bind(type)
.where("user_id = ?").bind(user->getId()).resultValue();
.where("user_id = ?").bind(userId).resultValue();
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session)
RangeResults<TrackListId>
TrackList::find(Session& session, UserId userId, Range range)
{
session.checkSharedLocked();
auto res = session.getDboSession().find<TrackList>().resultList();
return std::vector<TrackList::pointer>(res.begin(), res.end());
auto query {session.getDboSession().query<TrackListId>("SELECT id FROM tracklist")
.where("user_id = ?").bind(userId)
.orderBy("name COLLATE NOCASE")};
return execQuery(query, range);
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session, ObjectPtr<User> user)
RangeResults<TrackListId>
TrackList::find(Session& session, UserId userId, Type type, Range range)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user->getId())
.orderBy("name COLLATE NOCASE")
.resultList()};
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
std::vector<TrackList::pointer>
TrackList::getAll(Session& session, ObjectPtr<User> user, Type type)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<TrackList>()
.where("user_id = ?").bind(user->getId())
auto query {session.getDboSession().query<TrackListId>("SELECT id FROM tracklist")
.where("user_id = ?").bind(userId)
.where("type = ?").bind(type)
.orderBy("name COLLATE NOCASE")
.resultList()};
.orderBy("name COLLATE NOCASE")};
return std::vector<TrackList::pointer>(res.begin(), res.end());
return execQuery(query, range);
}
TrackList::pointer
TrackList::getById(Session& session, TrackListId id)
TrackList::find(Session& session, TrackListId id)
{
session.checkSharedLocked();
@@ -159,7 +158,7 @@ TrackList::getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTim
return session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(getId())
.where("track_id = ?").bind(track->getId())
.where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t()))
.where("date_time = ?").bind(normalizeDateTime(dateTime))
.resultValue();
}
@@ -270,19 +269,109 @@ createTracksQuery(Wt::Dbo::Session& session, TrackListId tracklistId, const std:
}
std::vector<Artist::pointer>
TrackList::getArtistsReverse(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
TrackList::getArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, ArtistSortMethod sortMethod, std::optional<Range> range, bool& moreResults) const
{
assert(session());
auto query {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType)
.groupBy("a.id").having("p_e.date_time = MAX(p_e.date_time)")
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)};
switch (sortMethod)
{
case ArtistSortMethod::None:
break;
case ArtistSortMethod::ByName:
query.orderBy("a.name COLLATE NOCASE");
break;
case ArtistSortMethod::BySortName:
query.orderBy("a.sort_name COLLATE NOCASE");
break;
case ArtistSortMethod::Random:
query.orderBy("RANDOM()");
break;
case ArtistSortMethod::LastWritten:
case ArtistSortMethod::StarredDateDesc:
assert(false); // Not implemented!
break;
}
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> collection {query.resultList()};
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<ObjectPtr<Release>>
TrackList::getReleases(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
auto collection {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds)
.groupBy("r.id").having("p_e.date_time = MAX(p_e.date_time)")
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<ObjectPtr<Track>>
TrackList::getTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
auto collection {createTracksQuery(*session(), getId(), clusterIds)
.groupBy("t.id").having("p_e.date_time = MAX(p_e.date_time)")
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
}
else
moreResults = false;
return res;
}
std::vector<Artist::pointer>
TrackList::getArtistsOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const
{
assert(session());
auto collection {createArtistsQuery(*session(), "SELECT a from artist a", getId(), clusterIds, linkType)
.groupBy("a.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.orderBy("p_e.date_time DESC, p_e.id DESC")
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
auto res {std::vector<Artist::pointer>(collection.begin(), collection.end())};
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
@@ -294,19 +383,19 @@ TrackList::getArtistsReverse(const std::vector<ClusterId>& clusterIds, std::opti
}
std::vector<Release::pointer>
TrackList::getReleasesReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getReleasesOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
auto collection {createReleasesQuery(*session(), "SELECT r from release r", getId(), clusterIds)
.groupBy("r.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.orderBy("p_e.date_time DESC, p_e.id DESC")
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
@@ -318,19 +407,19 @@ TrackList::getReleasesReverse(const std::vector<ClusterId>& clusterIds, std::opt
}
std::vector<Track::pointer>
TrackList::getTracksReverse(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
TrackList::getTracksOrderedByRecentFirst(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const
{
assert(session());
auto collection {createTracksQuery(*session(), getId(), clusterIds)
.groupBy("t.id").having("p_e.date_time = MAX(p_e.date_time)")
.orderBy("p_e.date_time DESC")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.orderBy("p_e.date_time DESC, p_e.id DESC")
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
@@ -424,13 +513,13 @@ TrackList::getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional
auto collection {query
.orderBy("COUNT(a.id) DESC")
.groupBy("a.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Artist::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
@@ -450,12 +539,12 @@ TrackList::getTopReleases(const std::vector<ClusterId>& clusterIds, std::optiona
auto collection {query
.orderBy("COUNT(r.id) DESC")
.groupBy("r.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Release::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
@@ -475,12 +564,12 @@ TrackList::getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<
auto collection {query
.orderBy("COUNT(t.id) DESC")
.groupBy("t.id")
.limit(range ? static_cast<int>(range->limit) + 1 : -1)
.limit(range ? static_cast<int>(range->size) + 1 : -1)
.offset(range ? static_cast<int>(range->offset) : -1)
.resultList()};
std::vector<Track::pointer> res(collection.begin(), collection.end());
if (range && res.size() == static_cast<std::size_t>(range->limit) + 1)
if (range && res.size() == static_cast<std::size_t>(range->size) + 1)
{
moreResults = true;
res.pop_back();
@@ -492,11 +581,10 @@ TrackList::getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<
}
TrackListEntry::TrackListEntry(ObjectPtr<Track> track, ObjectPtr<TrackList> tracklist, const Wt::WDateTime& dateTime)
: _dateTime {Wt::WDateTime::fromTime_t(dateTime.toTime_t())} // force second resolution
: _dateTime {normalizeDateTime(dateTime)}
, _track {getDboPtr(track)}
, _tracklist {getDboPtr(tracklist)}
{
assert(_dateTime.isValid());
}
TrackListEntry::pointer
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2015 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "services/database/Types.hpp"
#include <set>
namespace Database
{
static const std::set<Bitrate> allowedAudioBitrates
{
64000,
96000,
128000,
192000,
320000,
};
void visitAllowedAudioBitrates(std::function<void(Bitrate)> func)
{
for (Bitrate bitrate : allowedAudioBitrates)
func(bitrate);
}
bool isAudioBitrateAllowed(Bitrate bitrate)
{
return allowedAudioBitrates.find(bitrate) != std::cend(allowedAudioBitrates);
}
DateRange
DateRange::fromYearRange(int from, int to)
{
return DateRange {{from, 1, 1}, {to, 12, 31}};
}
}
+23 -144
View File
@@ -23,85 +23,18 @@
#include "services/database/Release.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "services/database/TrackList.hpp"
#include "utils/Logger.hpp"
#include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "Traits.hpp"
#include "Utils.hpp"
namespace Database {
AuthToken::AuthToken(const std::string& value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
: _value {value}
, _expiry {expiry}
, _user {getDboPtr(user)}
{
}
AuthToken::pointer
AuthToken::create(Session& session, const std::string& value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
{
session.checkUniqueLocked();
AuthToken::pointer res {session.getDboSession().add(std::make_unique<AuthToken>(value, expiry, user))};
session.getDboSession().flush();
return res;
}
void
AuthToken::removeExpiredTokens(Session& session, const Wt::WDateTime& now)
{
session.checkUniqueLocked();
session.getDboSession().execute
("DELETE FROM auth_token WHERE expiry < ?").bind(now);
}
AuthToken::pointer
AuthToken::getByValue(Session& session, const std::string& value)
{
session.checkSharedLocked();
return session.getDboSession().find<AuthToken>()
.where("value = ?").bind(value)
.resultValue();
}
static const std::string queuedListName {"__queued_tracks__"};
User::User(std::string_view loginName)
: _loginName {loginName}
{
}
std::vector<User::pointer>
User::getAll(Session& session)
{
session.checkSharedLocked();
auto res {session.getDboSession().find<User>().resultList()};
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<UserId>
User::getAllIds(Session& session)
{
session.checkSharedLocked();
auto res {session.getDboSession().query<UserId>("SELECT id FROM user").resultList()};
return std::vector<UserId>(res.begin(), res.end());
}
User::pointer
User::getDemo(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
}
std::size_t
User::getCount(Session& session)
{
@@ -110,28 +43,43 @@ User::getCount(Session& session)
return session.getDboSession().query<int>("SELECT COUNT(*) FROM user");
}
RangeResults<UserId>
User::find(Session& session, Range range)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<UserId>("SELECT id FROM user")};
return execQuery(query, range);
}
User::pointer
User::findDemoUser(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
}
User::pointer
User::create(Session& session, std::string_view loginName)
{
session.checkUniqueLocked();
User::pointer user {session.getDboSession().add(std::make_unique<User>(loginName))};
TrackList::create(session, queuedListName, TrackList::Type::Internal, false, user);
session.getDboSession().flush();
return user;
}
User::pointer
User::getById(Session& session, UserId id)
User::find(Session& session, UserId id)
{
return session.getDboSession().find<User>().where("id = ?").bind(id).resultValue();
}
User::pointer
User::getByLoginName(Session& session, std::string_view name)
User::find(Session& session, std::string_view name)
{
return session.getDboSession().find<User>()
.where("login_name = ?").bind(name)
@@ -141,7 +89,7 @@ User::getByLoginName(Session& session, std::string_view name)
void
User::setSubsonicTranscodeBitrate(Bitrate bitrate)
{
assert(audioTranscodeAllowedBitrates.find(bitrate) != audioTranscodeAllowedBitrates.cend());
assert(isAudioBitrateAllowed(bitrate));
_subsonicTranscodeBitrate = bitrate;
}
@@ -151,75 +99,6 @@ User::clearAuthTokens()
_authTokens.clear();
}
TrackList::pointer
User::getQueuedTrackList(Session& session) const
{
assert(self());
session.checkSharedLocked();
return TrackList::get(session, queuedListName, TrackList::Type::Internal, self());
}
void
User::star(ObjectPtr<Artist> artist)
{
if (_starredArtists.count(getDboPtr(artist)) == 0)
_starredArtists.insert(getDboPtr(artist));
}
void
User::unstar(ObjectPtr<Artist> artist)
{
if (_starredArtists.count(getDboPtr(artist)) != 0)
_starredArtists.erase(getDboPtr(artist));
}
bool
User::isStarred(ObjectPtr<Artist> artist) const
{
return _starredArtists.count(getDboPtr(artist)) != 0;
}
void
User::star(ObjectPtr<Release> release)
{
if (_starredReleases.count(getDboPtr(release)) == 0)
_starredReleases.insert(getDboPtr(release));
}
void
User::unstar(ObjectPtr<Release> release)
{
if (_starredReleases.count(getDboPtr(release)) != 0)
_starredReleases.erase(getDboPtr(release));
}
bool
User::isStarred(ObjectPtr<Release> release) const
{
return _starredReleases.count(getDboPtr(release)) != 0;
}
void
User::star(ObjectPtr<Track> track)
{
if (_starredTracks.count(getDboPtr(track)) == 0)
_starredTracks.insert(getDboPtr(track));
}
void
User::unstar(ObjectPtr<Track> track)
{
if (_starredTracks.count(getDboPtr(track)) != 0)
_starredTracks.erase(getDboPtr(track));
}
bool
User::isStarred(ObjectPtr<Track> track) const
{
return _starredTracks.count(getDboPtr(track)) != 0;
}
} // namespace Database
@@ -29,5 +29,12 @@ namespace Database
return StringUtils::escapeString(keyword, "%_", escapeChar);
}
Wt::WDateTime
normalizeDateTime(const Wt::WDateTime& dateTime)
{
// force second resolution
return Wt::WDateTime::fromTime_t(dateTime.toTime_t());
}
} // namespace Database
+55 -2
View File
@@ -21,13 +21,66 @@
#include <string>
#include <string_view>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#include "services/database/Types.hpp"
namespace Database
{
#define ESCAPE_CHAR_STR "\\"
static constexpr char escapeChar {'\\'};
static inline constexpr char escapeChar {'\\'};
std::string escapeLikeKeyword(std::string_view keywords);
template <typename T>
RangeResults<T>
execQuery(Wt::Dbo::Query<T>& query, Range range)
{
RangeResults<T> res;
auto collection {query.limit(range.size ? static_cast<int>(range.size) + 1 : -1)
.offset(range.offset ? static_cast<int>(range.offset) : -1)
.resultList()};
res.results.assign(collection.begin(), collection.end());
if (range.size && res.results.size() == static_cast<std::size_t>(range.size) + 1)
{
res.moreResults = true;
res.results.pop_back();
}
else
res.moreResults = false;
res.range.offset = range.offset;
res.range.size = res.results.size();
return res;
}
template <typename T>
RangeResults<typename T::pointer>
execQuery(Wt::Dbo::Query<Wt::Dbo::ptr<T>>& query, Range range)
{
RangeResults<typename T::pointer> res;
auto collection {query.limit(range.size ? static_cast<int>(range.size) + 1 : -1)
.offset(range.offset ? static_cast<int>(range.offset) : -1)
.resultList()};
res.results.assign(collection.begin(), collection.end());
if (range.size && res.results.size() == static_cast<std::size_t>(range.size) + 1)
{
res.moreResults = true;
res.results.pop_back();
}
else
res.moreResults = false;
res.range.offset = range.offset;
res.range.size = res.results.size();
return res;
}
Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime);
} // namespace Database