SubsonicAPI: optimized search for albums

This commit is contained in:
emeric
2023-11-04 16:15:39 +01:00
parent 4089f07c16
commit c4d2d20447
14 changed files with 735 additions and 718 deletions
+152 -127
View File
@@ -34,152 +34,170 @@
namespace Database namespace Database
{ {
namespace
Wt::Dbo::Query<ReleaseId> createQuery(Session& session, const Release::FindParameters& params)
{ {
auto query{ session.getDboSession().query<ReleaseId>("SELECT DISTINCT r.id from release r") }; template <typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Release::FindParameters& params)
if (params.sortMethod == ReleaseSortMethod::LastWritten
|| params.sortMethod == ReleaseSortMethod::Date
|| params.sortMethod == ReleaseSortMethod::OriginalDate
|| params.sortMethod == ReleaseSortMethod::OriginalDateDesc
|| params.writtenAfter.isValid()
|| params.dateRange
|| params.artist.isValid()
|| params.clusters.size() == 1)
{ {
query.join("track t ON t.release_id = r.id"); auto query{ session.getDboSession().query<ResultType>("SELECT DISTINCT " + std::string{ itemToSelect } + " from release r") };
}
if (params.writtenAfter.isValid()) if (params.sortMethod == ReleaseSortMethod::LastWritten
query.where("t.file_last_write > ?").bind(params.writtenAfter); || params.sortMethod == ReleaseSortMethod::Date
|| params.sortMethod == ReleaseSortMethod::OriginalDate
if (params.dateRange) || params.sortMethod == ReleaseSortMethod::OriginalDateDesc
{ || params.writtenAfter.isValid()
query.where("t.date >= ?").bind(params.dateRange->begin); || params.dateRange
query.where("t.date <= ?").bind(params.dateRange->end); || params.artist.isValid()
} || params.clusters.size() == 1)
for (std::string_view keyword : params.keywords)
query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + Utils::escapeLikeKeyword(keyword) + "%");
if (params.starringUser.isValid())
{
assert(params.feedbackBackend);
query.join("starred_release s_r ON s_r.release_id = r.id")
.where("s_r.user_id = ?").bind(params.starringUser)
.where("s_r.backend = ?").bind(*params.feedbackBackend)
.where("s_r.sync_state <> ?").bind(SyncState::PendingRemove);
}
if (params.artist.isValid())
{
query.join("artist a ON a.id = t_a_l.artist_id")
.join("track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(params.artist);
if (!params.trackArtistLinkTypes.empty())
{ {
std::ostringstream oss; query.join("track t ON t.release_id = r.id");
bool first{ true };
for (TrackArtistLinkType linkType : params.trackArtistLinkTypes)
{
if (!first)
oss << " OR ";
oss << "t_a_l.type = ?";
query.bind(linkType);
first = false;
}
query.where(oss.str());
} }
if (!params.excludedTrackArtistLinkTypes.empty()) 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("%" + Utils::escapeLikeKeyword(keyword) + "%");
if (params.starringUser.isValid())
{
assert(params.feedbackBackend);
query.join("starred_release s_r ON s_r.release_id = r.id")
.where("s_r.user_id = ?").bind(params.starringUser)
.where("s_r.backend = ?").bind(*params.feedbackBackend)
.where("s_r.sync_state <> ?").bind(SyncState::PendingRemove);
}
if (params.artist.isValid())
{
query.join("artist a ON a.id = t_a_l.artist_id")
.join("track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(params.artist);
if (!params.trackArtistLinkTypes.empty())
{
std::ostringstream oss;
bool first{ true };
for (TrackArtistLinkType linkType : params.trackArtistLinkTypes)
{
if (!first)
oss << " OR ";
oss << "t_a_l.type = ?";
query.bind(linkType);
first = false;
}
query.where(oss.str());
}
if (!params.excludedTrackArtistLinkTypes.empty())
{
std::ostringstream oss;
oss << "r.id NOT IN (SELECT DISTINCT r.id FROM release r"
" 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"
" INNER JOIN track t ON t.release_id = r.id"
" WHERE (a.id = ? AND (";
query.bind(params.artist);
bool first{ true };
for (const TrackArtistLinkType linkType : params.excludedTrackArtistLinkTypes)
{
if (!first)
oss << " OR ";
oss << "t_a_l.type = ?";
query.bind(linkType);
first = false;
}
oss << ")))";
query.where(oss.str());
}
}
if (params.clusters.size() == 1)
{
query.join("track_cluster t_c ON t_c.track_id = t.id")
.where("t_c.cluster_id = ?").bind(params.clusters.front());
}
else if (params.clusters.size() > 1)
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "r.id NOT IN (SELECT DISTINCT r.id FROM release r" oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" 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"
" INNER JOIN track t ON t.release_id = r.id" " INNER JOIN track t ON t.release_id = r.id"
" WHERE (a.id = ? AND ("; " INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
query.bind(params.artist); WhereClause clusterClause;
for (const ClusterId clusterId : params.clusters)
bool first{ true };
for (const TrackArtistLinkType linkType : params.excludedTrackArtistLinkTypes)
{ {
if (!first) clusterClause.Or(WhereClause("t_c.cluster_id = ?"));
oss << " OR "; query.bind(clusterId);
oss << "t_a_l.type = ?";
query.bind(linkType);
first = false;
} }
oss << ")))";
oss << " " << clusterClause.get();
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
query.where(oss.str()); query.where(oss.str());
} }
}
if (params.clusters.size() == 1) if (params.primaryType)
{ query.where("primary_type = ?").bind(*params.primaryType);
query.join("track_cluster t_c ON t_c.track_id = t.id") if (!params.secondaryTypes.empty())
.where("t_c.cluster_id = ?").bind(params.clusters.front()); query.where("secondary_type = ?").bind(params.secondaryTypes);
}
else if (params.clusters.size() > 1)
{
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 track_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause; switch (params.sortMethod)
for (const ClusterId clusterId : params.clusters)
{ {
clusterClause.Or(WhereClause("t_c.cluster_id = ?")); case ReleaseSortMethod::None:
query.bind(clusterId); 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::OriginalDate:
query.orderBy("CASE WHEN t.original_date IS NULL THEN t.date ELSE t.original_date END, t.date, r.name COLLATE NOCASE");
break;
case ReleaseSortMethod::OriginalDateDesc:
query.orderBy("CASE WHEN t.original_date IS NULL THEN t.date ELSE t.original_date END DESC, t.date, r.name COLLATE NOCASE");
break;
case ReleaseSortMethod::StarredDateDesc:
assert(params.starringUser.isValid());
query.orderBy("s_r.date_time DESC");
break;
} }
oss << " " << clusterClause.get(); return query;
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
query.where(oss.str());
} }
if (params.primaryType) template <typename ResultType>
query.where("primary_type = ?").bind(*params.primaryType); Wt::Dbo::Query<ResultType> createQuery(Session& session, const Release::FindParameters& params)
if (!params.secondaryTypes.empty())
query.where("secondary_type = ?").bind(params.secondaryTypes);
switch (params.sortMethod)
{ {
case ReleaseSortMethod::None: std::string_view itemToSelect;
break;
case ReleaseSortMethod::Name: if constexpr (std::is_same_v<ResultType, ReleaseId>)
query.orderBy("r.name COLLATE NOCASE"); itemToSelect = "r.id";
break; else if constexpr (std::is_same_v<ResultType, Wt::Dbo::ptr<Release>>)
case ReleaseSortMethod::Random: itemToSelect = "r";
query.orderBy("RANDOM()"); else
break; static_assert("Unhandled type");
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::OriginalDate:
query.orderBy("CASE WHEN t.original_date IS NULL THEN t.date ELSE t.original_date END, t.date, r.name COLLATE NOCASE");
break;
case ReleaseSortMethod::OriginalDateDesc:
query.orderBy("CASE WHEN t.original_date IS NULL THEN t.date ELSE t.original_date END DESC, t.date, r.name COLLATE NOCASE");
break;
case ReleaseSortMethod::StarredDateDesc:
assert(params.starringUser.isValid());
query.orderBy("s_r.date_time DESC");
break;
}
return query; return createQuery<ResultType>(session, itemToSelect, params);
}
} }
Release::Release(const std::string& name, const std::optional<UUID>& MBID) Release::Release(const std::string& name, const std::optional<UUID>& MBID)
@@ -238,7 +256,7 @@ namespace Database
return session.getDboSession().query<int>("SELECT COUNT(*) FROM release"); return session.getDboSession().query<int>("SELECT COUNT(*) FROM release");
} }
RangeResults<ReleaseId> Release::findOrderedByArtist(Session& session, Range range) RangeResults<ReleaseId> Release::findIdsOrderedByArtist(Session& session, Range range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
@@ -253,7 +271,7 @@ namespace Database
return Utils::execQuery(query, range); return Utils::execQuery(query, range);
} }
RangeResults<ReleaseId> Release::findOrphans(Session& session, Range range) RangeResults<ReleaseId> Release::findOrphanIds(Session& session, Range range)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
@@ -261,12 +279,19 @@ namespace Database
return Utils::execQuery(query, range); return Utils::execQuery(query, range);
} }
RangeResults<ReleaseId> Release::find(Session& session, const FindParameters& params) RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params)
{ {
session.checkSharedLocked(); session.checkSharedLocked();
auto query{ createQuery(session, params) }; auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, params) };
return Utils::execQuery(query, params.range);
}
RangeResults<ReleaseId> Release::findIds(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
auto query{ createQuery<ReleaseId>(session, params) };
return Utils::execQuery(query, params.range); return Utils::execQuery(query, params.range);
} }
@@ -88,9 +88,10 @@ namespace Database
static pointer find(Session& session, const UUID& MBID); static pointer find(Session& session, const UUID& MBID);
static std::vector<pointer> find(Session& session, const std::string& name); static std::vector<pointer> find(Session& session, const std::string& name);
static pointer find(Session& session, ReleaseId id); static pointer find(Session& session, ReleaseId id);
static RangeResults<ReleaseId> find(Session& session, const FindParameters& parameters); static RangeResults<pointer> find(Session& session, const FindParameters& parameters);
static RangeResults<ReleaseId> findOrphans(Session& session, Range range); // no track related static RangeResults<ReleaseId> findIds(Session& session, const FindParameters& parameters);
static RangeResults<ReleaseId> findOrderedByArtist(Session& session, Range range); static RangeResults<ReleaseId> findOrphanIds(Session& session, Range range); // not track related
static RangeResults<ReleaseId> findIdsOrderedByArtist(Session& session, Range range);
// Get the cluster of the tracks that belong to this release // Get the cluster of the tracks that belong to this release
// Each clusters are grouped by cluster type, sorted by the number of occurence (max to min) // Each clusters are grouped by cluster type, sorted by the number of occurence (max to min)
+9 -9
View File
@@ -374,7 +374,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
auto releases{ Release::find(session, Release::FindParameters {}.setClusters({cluster.getId()})) }; const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({cluster.getId()})) };
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
} }
@@ -382,7 +382,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
auto releases{ Release::find(session, Release::FindParameters {}.setClusters({unusedCluster.getId()})) }; const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({unusedCluster.getId()})) };
EXPECT_EQ(releases.results.size(), 0); EXPECT_EQ(releases.results.size(), 0);
} }
@@ -415,7 +415,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters)
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty());
EXPECT_EQ(Cluster::findOrphans(session, Range{}).results.size(), 2); EXPECT_EQ(Cluster::findOrphans(session, Range{}).results.size(), 2);
EXPECT_TRUE(Release::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty());
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
} }
@@ -475,7 +475,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty());
EXPECT_TRUE(Release::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty());
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
} }
@@ -628,7 +628,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster)
EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Cluster::findOrphans(session, Range{}).results.empty());
EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(ClusterType::findOrphans(session, Range{}).results.empty());
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
EXPECT_TRUE(Release::findOrphans(session, Range{}).results.empty()); EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty());
} }
{ {
@@ -638,11 +638,11 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster)
ASSERT_EQ(artists.results.size(), 1); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); EXPECT_EQ(artists.results.front(), artist.getId());
auto releases{ Release::find(session, Release::FindParameters {}.setArtist(artist.getId())) }; auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) };
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
releases = Release::find(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster.getId() })); releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster.getId() }));
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
} }
@@ -669,11 +669,11 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
auto releases{ Release::find(session, Release::FindParameters {}.setArtist(artist.getId())) }; auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) };
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
releases = Release::find(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster1.getId(), cluster2.getId() })); releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster1.getId(), cluster2.getId() }));
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
} }
@@ -42,14 +42,14 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease)
{ {
auto transaction {session.createSharedTransaction()}; auto transaction {session.createSharedTransaction()};
EXPECT_TRUE(Release::findOrphans(session, Range {}).results.empty()); EXPECT_TRUE(Release::findOrphanIds(session, Range {}).results.empty());
EXPECT_TRUE(Artist::findAllOrphans(session, Range {}).results.empty()); EXPECT_TRUE(Artist::findAllOrphans(session, Range {}).results.empty());
} }
{ {
auto transaction {session.createSharedTransaction()}; auto transaction {session.createSharedTransaction()};
auto releases {Release::find(session, Release::FindParameters {}.setArtist(artist.getId()))}; auto releases {Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId()))};
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
@@ -74,7 +74,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtist)
{ {
auto transaction {session.createUniqueTransaction()}; auto transaction {session.createUniqueTransaction()};
auto releases {Release::find(session, Release::FindParameters {}.setArtist(artist.getId()))}; auto releases {Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId()))};
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(releases.results.front(), release.getId());
File diff suppressed because it is too large Load Diff
@@ -96,7 +96,7 @@ TEST_F(DatabaseFixture, StarredRelease_dateTime)
{ {
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
auto releases{ Release::find(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) };
EXPECT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results.size(), 2);
} }
@@ -106,7 +106,7 @@ TEST_F(DatabaseFixture, StarredRelease_dateTime)
starredRelease1.get().modify()->setDateTime(dateTime); starredRelease1.get().modify()->setDateTime(dateTime);
starredRelease2.get().modify()->setDateTime(dateTime.addSecs(-1)); starredRelease2.get().modify()->setDateTime(dateTime.addSecs(-1));
auto releases{ Release::find(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) };
ASSERT_EQ(releases.results.size(), 2); ASSERT_EQ(releases.results.size(), 2);
EXPECT_EQ(releases.results[0], starredRelease1->getRelease()->getId()); EXPECT_EQ(releases.results[0], starredRelease1->getRelease()->getId());
EXPECT_EQ(releases.results[1], starredRelease2->getRelease()->getId()); EXPECT_EQ(releases.results[1], starredRelease2->getRelease()->getId());
@@ -117,7 +117,7 @@ TEST_F(DatabaseFixture, StarredRelease_dateTime)
starredRelease1.get().modify()->setDateTime(dateTime); starredRelease1.get().modify()->setDateTime(dateTime);
starredRelease2.get().modify()->setDateTime(dateTime.addSecs(1)); starredRelease2.get().modify()->setDateTime(dateTime.addSecs(1));
auto releases{ Release::find(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) };
ASSERT_EQ(releases.results.size(), 2); ASSERT_EQ(releases.results.size(), 2);
EXPECT_EQ(releases.results[0], starredRelease2->getRelease()->getId()); EXPECT_EQ(releases.results[0], starredRelease2->getRelease()->getId());
EXPECT_EQ(releases.results[1], starredRelease1->getRelease()->getId()); EXPECT_EQ(releases.results[1], starredRelease1->getRelease()->getId());
@@ -141,7 +141,7 @@ namespace Feedback
Session& session{ _db.getTLSSession() }; Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() }; auto transaction{ session.createSharedTransaction() };
return Release::find(session, params); return Release::findIds(session, params);
} }
void FeedbackService::star(UserId userId, TrackId trackId) void FeedbackService::star(UserId userId, TrackId trackId)
@@ -153,10 +153,11 @@ namespace Scanner
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases..."; LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases...";
// TODO, by batch
Session& session {_db.getTLSSession()}; Session& session {_db.getTLSSession()};
auto transaction {session.createUniqueTransaction()}; auto transaction {session.createUniqueTransaction()};
auto releases {Release::findOrphans(session, Range {})}; auto releases {Release::findOrphanIds(session, Range {})};
for (const ReleaseId releaseId : releases.results) for (const ReleaseId releaseId : releases.results)
{ {
Release::pointer release {Release::find(session, releaseId)}; Release::pointer release {Release::find(session, releaseId)};
@@ -66,11 +66,11 @@ namespace API::Subsonic
params.setSortMethod(ReleaseSortMethod::Name); params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range); params.setRange(range);
releases = Release::find(context.dbSession, params); releases = Release::findIds(context.dbSession, params);
} }
else if (type == "alphabeticalByArtist") else if (type == "alphabeticalByArtist")
{ {
releases = Release::findOrderedByArtist(context.dbSession, range); releases = Release::findIdsOrderedByArtist(context.dbSession, range);
} }
else if (type == "byGenre") else if (type == "byGenre")
{ {
@@ -86,7 +86,7 @@ namespace API::Subsonic
params.setSortMethod(ReleaseSortMethod::Name); params.setSortMethod(ReleaseSortMethod::Name);
params.setRange(range); params.setRange(range);
releases = Release::find(context.dbSession, params); releases = Release::findIds(context.dbSession, params);
} }
} }
} }
@@ -100,7 +100,7 @@ namespace API::Subsonic
params.setRange(range); params.setRange(range);
params.setDateRange(DateRange::fromYearRange(fromYear, toYear)); params.setDateRange(DateRange::fromYearRange(fromYear, toYear));
releases = Release::find(context.dbSession, params); releases = Release::findIds(context.dbSession, params);
} }
else if (type == "frequent") else if (type == "frequent")
{ {
@@ -112,7 +112,7 @@ namespace API::Subsonic
params.setSortMethod(ReleaseSortMethod::LastWritten); params.setSortMethod(ReleaseSortMethod::LastWritten);
params.setRange(range); params.setRange(range);
releases = Release::find(context.dbSession, params); releases = Release::findIds(context.dbSession, params);
} }
else if (type == "random") else if (type == "random")
{ {
@@ -122,7 +122,7 @@ namespace API::Subsonic
params.setSortMethod(ReleaseSortMethod::Random); params.setSortMethod(ReleaseSortMethod::Random);
params.setRange({ 0, size }); params.setRange({ 0, size });
releases = Release::find(context.dbSession, params); releases = Release::findIds(context.dbSession, params);
} }
else if (type == "recent") else if (type == "recent")
{ {
@@ -335,11 +335,8 @@ namespace API::Subsonic
directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(artist->getName())); directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(artist->getName()));
const auto artistReleases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(*artistId)) }; const auto artistReleases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(*artistId)) };
for (const ReleaseId artistReleaseId : artistReleases.results) for (const Release::pointer& release : artistReleases.results)
{
const Release::pointer release{ Release::find(context.dbSession, artistReleaseId) };
directoryNode.addArrayChild("child", createAlbumNode(context, release, user, false /* no id3 */)); directoryNode.addArrayChild("child", createAlbumNode(context, release, user, false /* no id3 */));
}
} }
else if (releaseId) else if (releaseId)
{ {
@@ -408,11 +405,8 @@ namespace API::Subsonic
Response::Node artistNode{ createArtistNode(context, artist, user, true /* id3 */) }; Response::Node artistNode{ createArtistNode(context, artist, user, true /* id3 */) };
const auto releases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(artist->getId())) }; const auto releases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(artist->getId())) };
for (const ReleaseId releaseId : releases.results) for (const Release::pointer& release : releases.results)
{
const Release::pointer release{ Release::find(context.dbSession, releaseId) };
artistNode.addArrayChild("album", createAlbumNode(context, release, user, true /* id3 */)); artistNode.addArrayChild("album", createAlbumNode(context, release, user, true /* id3 */));
}
response.addNode("artist", std::move(artistNode)); response.addNode("artist", std::move(artistNode));
@@ -70,7 +70,6 @@ namespace API::Subsonic
{ {
Artist::FindParameters params; Artist::FindParameters params;
params.setKeywords(keywords); params.setKeywords(keywords);
params.setSortMethod(ArtistSortMethod::BySortName);
params.setRange({ artistOffset, artistCount }); params.setRange({ artistOffset, artistCount });
RangeResults<ArtistId> artistIds{ Artist::find(context.dbSession, params) }; RangeResults<ArtistId> artistIds{ Artist::find(context.dbSession, params) };
@@ -85,15 +84,11 @@ namespace API::Subsonic
{ {
Release::FindParameters params; Release::FindParameters params;
params.setKeywords(keywords); params.setKeywords(keywords);
params.setSortMethod(ReleaseSortMethod::Name);
params.setRange({ albumOffset, albumCount }); params.setRange({ albumOffset, albumCount });
RangeResults<ReleaseId> releaseIds{ Release::find(context.dbSession, params) }; RangeResults<Release::pointer> releases{ Release::find(context.dbSession, params) };
for (const ReleaseId releaseId : releaseIds.results) for (const Release::pointer& release : releases.results)
{
const auto release{ Release::find(context.dbSession, releaseId) };
searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3)); searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3));
}
} }
if (songCount > 0) if (songCount > 0)
+2 -2
View File
@@ -216,7 +216,7 @@ Artist::refreshReleases()
params.setArtist(_artistId, {TrackArtistLinkType::ReleaseArtist}, {}); params.setArtist(_artistId, {TrackArtistLinkType::ReleaseArtist}, {});
params.setSortMethod(ReleaseSortMethod::OriginalDateDesc); params.setSortMethod(ReleaseSortMethod::OriginalDateDesc);
const auto releases {Release::find(LmsApp->getDbSession(), params)}; const auto releases {Release::findIds(LmsApp->getDbSession(), params)};
if (!releases.results.empty()) if (!releases.results.empty())
{ {
// first pass: gather all ids and sort by type // first pass: gather all ids and sort by type
@@ -276,7 +276,7 @@ Artist::refreshAppearsOnReleases()
params.setArtist(_artistId, types, {TrackArtistLinkType::ReleaseArtist}); params.setArtist(_artistId, types, {TrackArtistLinkType::ReleaseArtist});
params.setSortMethod(ReleaseSortMethod::OriginalDateDesc); params.setSortMethod(ReleaseSortMethod::OriginalDateDesc);
const auto releases {Release::find(LmsApp->getDbSession(), params)}; const auto releases {Release::findIds(LmsApp->getDbSession(), params)};
if (!releases.results.empty()) if (!releases.results.empty())
{ {
Wt::WTemplate* releaseContainer {bindNew<Wt::WTemplate>("appears-on-releases", Wt::WString::tr("Lms.Explore.Artist.template.release-container"))}; Wt::WTemplate* releaseContainer {bindNew<Wt::WTemplate>("appears-on-releases", Wt::WString::tr("Lms.Explore.Artist.template.release-container"))};
+6 -11
View File
@@ -19,12 +19,9 @@
#include "ReleaseCollector.hpp" #include "ReleaseCollector.hpp"
#include <algorithm>
#include "services/database/Release.hpp" #include "services/database/Release.hpp"
#include "services/database/Session.hpp" #include "services/database/Session.hpp"
#include "services/database/User.hpp" #include "services/database/User.hpp"
#include "services/database/TrackList.hpp"
#include "services/feedback/IFeedbackService.hpp" #include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp" #include "services/scrobbling/IScrobblingService.hpp"
#include "utils/Service.hpp" #include "utils/Service.hpp"
@@ -35,8 +32,7 @@ namespace UserInterface
{ {
using namespace Database; using namespace Database;
RangeResults<ReleaseId> RangeResults<ReleaseId> ReleaseCollector::get(Database::Range range)
ReleaseCollector::get(Database::Range range)
{ {
Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() }; Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() };
Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() }; Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() };
@@ -73,7 +69,7 @@ namespace UserInterface
{ {
auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
releases = Release::find(LmsApp->getDbSession(), params); releases = Release::findIds(LmsApp->getDbSession(), params);
} }
break; break;
} }
@@ -87,7 +83,7 @@ namespace UserInterface
{ {
auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
releases = Release::find(LmsApp->getDbSession(), params); releases = Release::findIds(LmsApp->getDbSession(), params);
} }
break; break;
} }
@@ -101,7 +97,7 @@ namespace UserInterface
{ {
auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
releases = Release::find(LmsApp->getDbSession(), params); releases = Release::findIds(LmsApp->getDbSession(), params);
} }
break; break;
} }
@@ -113,8 +109,7 @@ namespace UserInterface
return releases; return releases;
} }
RangeResults<ReleaseId> RangeResults<ReleaseId> ReleaseCollector::getRandomReleases(Range range)
ReleaseCollector::getRandomReleases(Range range)
{ {
assert(getMode() == Mode::Random); assert(getMode() == Mode::Random);
@@ -127,7 +122,7 @@ namespace UserInterface
{ {
auto transaction{ LmsApp->getDbSession().createSharedTransaction() }; auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
_randomReleases = Release::find(LmsApp->getDbSession(), params); _randomReleases = Release::findIds(LmsApp->getDbSession(), params);
} }
} }
+103 -103
View File
@@ -42,154 +42,154 @@ static
void void
dumpTracksRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount) dumpTracksRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
{ {
const RangeResults<TrackId> trackIds {[&]() const RangeResults<TrackId> trackIds{ [&]
{ {
auto transaction {session.createSharedTransaction()}; auto transaction {session.createSharedTransaction()};
return Track::find(session, Track::FindParameters {}); return Track::find(session, Track::FindParameters {});
}()}; }() };
std::cout << "*** Tracks (" << trackIds.results.size() << ") ***" << std::endl; std::cout << "*** Tracks (" << trackIds.results.size() << ") ***" << std::endl;
for (const TrackId trackId : trackIds.results) for (const TrackId trackId : trackIds.results)
{ {
auto trackToString = [&](const TrackId trackId) auto trackToString = [&](const TrackId trackId)
{ {
std::string res; std::string res;
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
const Track::pointer track {Track::find(session, trackId)}; const Track::pointer track{ Track::find(session, trackId) };
res += track->getName(); res += track->getName();
if (track->getRelease()) if (track->getRelease())
res += " [" + track->getRelease()->getName() + "]"; res += " [" + track->getRelease()->getName() + "]";
for (auto artist : track->getArtists({TrackArtistLinkType::Artist})) for (auto artist : track->getArtists({ TrackArtistLinkType::Artist }))
res += " - " + artist->getName(); res += " - " + artist->getName();
for (auto cluster : track->getClusters()) for (auto cluster : track->getClusters())
res += " {" + cluster->getType()->getName() + "-"+ cluster->getName() + "}"; res += " {" + cluster->getType()->getName() + "-" + cluster->getName() + "}";
return res; return res;
}; };
std::cout << "Processing track '" << trackToString(trackId) << std::endl; std::cout << "Processing track '" << trackToString(trackId) << std::endl;
for (TrackId similarTrackId : recommendationService.findSimilarTracks({trackId}, maxSimilarityCount)) for (TrackId similarTrackId : recommendationService.findSimilarTracks({ trackId }, maxSimilarityCount))
std::cout << "\t- Similar track '" << trackToString(similarTrackId) << std::endl; std::cout << "\t- Similar track '" << trackToString(similarTrackId) << std::endl;
} }
} }
static static
void void
dumpReleasesRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount) dumpReleasesRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
{ {
const RangeResults<ReleaseId> releaseIds {std::invoke([&]() const RangeResults<ReleaseId> releaseIds{ std::invoke([&]
{ {
auto transaction {session.createSharedTransaction()}; auto transaction {session.createSharedTransaction()};
return Release::find(session, Release::FindParameters {}); return Release::findIds(session, Release::FindParameters {});
})}; }) };
std::cout << "*** Releases ***" << std::endl; std::cout << "*** Releases ***" << std::endl;
for (ReleaseId releaseId : releaseIds.results) for (const ReleaseId releaseId : releaseIds.results)
{ {
auto releaseToString = [&](ReleaseId releaseId) -> std::string auto releaseToString = [&](ReleaseId releaseId) -> std::string
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
Release::pointer release {Release::find(session, releaseId)}; Release::pointer release{ Release::find(session, releaseId) };
return release->getName(); return release->getName();
}; };
std::cout << "Processing release '" << releaseToString(releaseId) << "'" << std::endl; std::cout << "Processing release '" << releaseToString(releaseId) << "'" << std::endl;
for (ReleaseId similarReleaseId : recommendationService.getSimilarReleases(releaseId, maxSimilarityCount)) for (const ReleaseId similarReleaseId : recommendationService.getSimilarReleases(releaseId, maxSimilarityCount))
std::cout << "\t- Similar release '" << releaseToString(similarReleaseId) << "'" << std::endl; std::cout << "\t- Similar release '" << releaseToString(similarReleaseId) << "'" << std::endl;
} }
} }
static static
void void
dumpArtistsRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount) dumpArtistsRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
{ {
const RangeResults<ArtistId> artistIds = std::invoke([&]() const RangeResults<ArtistId> artistIds = std::invoke([&]()
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
return Artist::find(session, Artist::FindParameters {}); return Artist::find(session, Artist::FindParameters{});
}); });
std::cout << "*** Artists ***" << std::endl; std::cout << "*** Artists ***" << std::endl;
for (ArtistId artistId : artistIds.results) for (ArtistId artistId : artistIds.results)
{ {
auto artistToString = [&](ArtistId artistId) auto artistToString = [&](ArtistId artistId)
{ {
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
Artist::pointer artist {Artist::find(session, artistId)}; Artist::pointer artist{ Artist::find(session, artistId) };
return artist->getName(); return artist->getName();
}; };
std::cout << "Processing artist '" << artistToString(artistId) << "'" << std::endl; std::cout << "Processing artist '" << artistToString(artistId) << "'" << std::endl;
for (ArtistId similarArtistId : recommendationService.getSimilarArtists(artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, maxSimilarityCount)) for (ArtistId similarArtistId : recommendationService.getSimilarArtists(artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, maxSimilarityCount))
{ {
std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl; std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl;
} }
} }
} }
int main(int argc, char *argv[]) int main(int argc, char* argv[])
{ {
try try
{ {
namespace po = boost::program_options; namespace po = boost::program_options;
// log to stdout // log to stdout
Service<Logger> logger {std::make_unique<StreamLogger>(std::cout)}; Service<Logger> logger{ std::make_unique<StreamLogger>(std::cout) };
po::options_description desc{"Allowed options"}; po::options_description desc{ "Allowed options" };
desc.add_options() desc.add_options()
("help,h", "print usage message") ("help,h", "print usage message")
("conf,c", po::value<std::string>()->default_value("/etc/lms.conf"), "LMS config file") ("conf,c", po::value<std::string>()->default_value("/etc/lms.conf"), "LMS config file")
("artists,a", "Display recommendation for artists") ("artists,a", "Display recommendation for artists")
("releases,r", "Display recommendation for releases") ("releases,r", "Display recommendation for releases")
("tracks,t", "Display recommendation for tracks") ("tracks,t", "Display recommendation for tracks")
("max,m", po::value<unsigned>()->default_value(3), "Max similarity result count") ("max,m", po::value<unsigned>()->default_value(3), "Max similarity result count")
; ;
po::variables_map vm; po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm); po::store(po::parse_command_line(argc, argv, desc), vm);
if (vm.count("help")) if (vm.count("help"))
{ {
std::cout << desc << std::endl; std::cout << desc << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
Service<IConfig> config {createConfig(vm["conf"].as<std::string>())}; Service<IConfig> config{ createConfig(vm["conf"].as<std::string>()) };
Db db {config->getPath("working-dir") / "lms.db"}; Db db{ config->getPath("working-dir") / "lms.db" };
Session session {db}; Session session{ db };
std::cout << "Creating recommendation recommendationService..." << std::endl; std::cout << "Creating recommendation recommendationService..." << std::endl;
const auto recommendationService {Recommendation::createRecommendationService(db)}; const auto recommendationService{ Recommendation::createRecommendationService(db) };
std::cout << "Recommendation recommendationService created!" << std::endl; std::cout << "Recommendation recommendationService created!" << std::endl;
std::cout << "Loading recommendation recommendationService..." << std::endl; std::cout << "Loading recommendation recommendationService..." << std::endl;
recommendationService->load(false); recommendationService->load(false);
unsigned maxSimilarityCount {vm["max"].as<unsigned>()}; unsigned maxSimilarityCount{ vm["max"].as<unsigned>() };
std::cout << "Recommendation recommendationService loaded!" << std::endl; std::cout << "Recommendation recommendationService loaded!" << std::endl;
if (vm.count("tracks")) if (vm.count("tracks"))
dumpTracksRecommendation(db, *recommendationService, maxSimilarityCount); dumpTracksRecommendation(db, *recommendationService, maxSimilarityCount);
if (vm.count("releases")) if (vm.count("releases"))
dumpReleasesRecommendation(db, *recommendationService, maxSimilarityCount); dumpReleasesRecommendation(db, *recommendationService, maxSimilarityCount);
if (vm.count("artists")) if (vm.count("artists"))
dumpArtistsRecommendation(db, *recommendationService, maxSimilarityCount); dumpArtistsRecommendation(db, *recommendationService, maxSimilarityCount);
} }
catch( std::exception& e) catch (std::exception& e)
{ {
std::cerr << "Caught exception: " << e.what() << std::endl; std::cerr << "Caught exception: " << e.what() << std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }