Added a dedicated table for release artists + handle display names in UI/Subsonic API, ref #731
This commit is contained in:
@@ -14,9 +14,7 @@ add_library(lmsdatabase STATIC
|
||||
impl/objects/PlayQueue.cpp
|
||||
impl/objects/Podcast.cpp
|
||||
impl/objects/PodcastEpisode.cpp
|
||||
impl/objects/TrackArtistLink.cpp
|
||||
impl/objects/TrackFeatures.cpp
|
||||
impl/objects/TrackList.cpp
|
||||
impl/objects/ReleaseArtistLink.cpp
|
||||
impl/objects/RatedArtist.cpp
|
||||
impl/objects/RatedRelease.cpp
|
||||
impl/objects/RatedTrack.cpp
|
||||
@@ -26,9 +24,12 @@ add_library(lmsdatabase STATIC
|
||||
impl/objects/StarredRelease.cpp
|
||||
impl/objects/StarredTrack.cpp
|
||||
impl/objects/Track.cpp
|
||||
impl/objects/TrackArtistLink.cpp
|
||||
impl/objects/TrackBookmark.cpp
|
||||
impl/objects/TrackEmbeddedImage.cpp
|
||||
impl/objects/TrackEmbeddedImageLink.cpp
|
||||
impl/objects/TrackFeatures.cpp
|
||||
impl/objects/TrackList.cpp
|
||||
impl/objects/TrackLyrics.cpp
|
||||
impl/objects/Types.cpp
|
||||
impl/objects/UIState.cpp
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 102 };
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 103 };
|
||||
}
|
||||
|
||||
VersionInfo::VersionInfo()
|
||||
@@ -1681,6 +1681,31 @@ FROM track)");
|
||||
utils::executeCommand(*session.getDboSession(), "ALTER TABLE podcast_episode ADD bits_per_sample INTEGER");
|
||||
}
|
||||
|
||||
void migrateFromV102(Session& session)
|
||||
{
|
||||
// New link to artists from releases
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "release_artist_link" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"artist_name" text not null,
|
||||
"artist_sort_name" text not null,
|
||||
"artist_mbid_matched" boolean not null,
|
||||
"release_id" bigint,
|
||||
"artist_id" bigint,
|
||||
constraint "fk_release_artist_link_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_release_artist_link_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred
|
||||
))");
|
||||
|
||||
// Remove all artist links where the link type is TrackArtistLinkType::ReleaseArtist = 8
|
||||
utils::executeCommand(*session.getDboSession(), R"(DELETE FROM track_artist_link WHERE type = 8)");
|
||||
|
||||
// Remove outdated UI state entries
|
||||
utils::executeCommand(*session.getDboSession(), R"(DELETE FROM ui_state WHERE item = 'artists_link_type')");
|
||||
|
||||
// Just increment the scan version of the settings to make the next scan rescan all audio files
|
||||
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET audio_scan_version = audio_scan_version + 1");
|
||||
}
|
||||
|
||||
bool doDbMigration(Session& session)
|
||||
{
|
||||
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||
@@ -1759,6 +1784,7 @@ FROM track)");
|
||||
{ 99, migrateFromV99 },
|
||||
{ 100, migrateFromV100 },
|
||||
{ 101, migrateFromV101 },
|
||||
{ 102, migrateFromV102 },
|
||||
};
|
||||
|
||||
bool migrationPerformed{};
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "database/objects/RatedRelease.hpp"
|
||||
#include "database/objects/RatedTrack.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/ScanSettings.hpp"
|
||||
#include "database/objects/StarredArtist.hpp"
|
||||
#include "database/objects/StarredRelease.hpp"
|
||||
@@ -94,6 +95,7 @@ namespace lms::db
|
||||
_session.mapClass<RatedRelease>("rated_release");
|
||||
_session.mapClass<RatedTrack>("rated_track");
|
||||
_session.mapClass<Release>("release");
|
||||
_session.mapClass<ReleaseArtistLink>("release_artist_link");
|
||||
_session.mapClass<ReleaseType>("release_type");
|
||||
_session.mapClass<ScanSettings>("scan_settings");
|
||||
_session.mapClass<StarredArtist>("starred_artist");
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
// #include "database/objects/StarredArtist.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
// #include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
@@ -53,9 +51,8 @@ namespace lms::db
|
||||
if (params.sortMethod == ArtistSortMethod::LastWrittenDesc
|
||||
|| params.sortMethod == ArtistSortMethod::AddedDesc
|
||||
|| params.writtenAfter.isValid()
|
||||
|| params.linkType
|
||||
|| params.trackArtistLinkType.has_value()
|
||||
|| params.track.isValid()
|
||||
|| params.release.isValid()
|
||||
|| params.filters.clusters.size() == 1
|
||||
|| params.filters.codec.has_value()
|
||||
|| params.filters.mediaLibrary.isValid()
|
||||
@@ -68,7 +65,6 @@ namespace lms::db
|
||||
if (params.sortMethod == ArtistSortMethod::LastWrittenDesc
|
||||
|| params.sortMethod == ArtistSortMethod::AddedDesc
|
||||
|| params.writtenAfter.isValid()
|
||||
|| params.release.isValid()
|
||||
|| params.filters.codec.has_value()
|
||||
|| params.filters.mediaLibrary.isValid()
|
||||
|| params.filters.label.isValid()
|
||||
@@ -79,9 +75,6 @@ namespace lms::db
|
||||
if (params.writtenAfter.isValid())
|
||||
query.where("t.file_last_write > ?").bind(params.writtenAfter);
|
||||
|
||||
if (params.release.isValid())
|
||||
query.where("t.release_id = ?").bind(params.release);
|
||||
|
||||
if (params.filters.codec.has_value())
|
||||
query.where("t.codec = ?").bind(detail::getDbCodec(*params.filters.codec));
|
||||
|
||||
@@ -101,8 +94,11 @@ namespace lms::db
|
||||
}
|
||||
}
|
||||
|
||||
if (params.linkType)
|
||||
query.where("+t_a_l.type = ?").bind(*params.linkType); // Exclude this since the query planner does not do a good job when db is not analyzed
|
||||
if (params.releaseArtistsOnly)
|
||||
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
|
||||
|
||||
if (params.trackArtistLinkType.has_value())
|
||||
query.where("+t_a_l.type = ?").bind(*params.trackArtistLinkType); // Exclude this since the query planner does not do a good job when db is not analyzed
|
||||
|
||||
if (!params.keywords.empty())
|
||||
{
|
||||
@@ -318,6 +314,7 @@ namespace lms::db
|
||||
|
||||
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
// TODO extend with release artists
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession()->query<ArtistId>(R"(SELECT DISTINCT a.id FROM artist a
|
||||
WHERE NOT EXISTS (
|
||||
@@ -327,6 +324,12 @@ WHERE NOT EXISTS (
|
||||
ON t_a_l.artist_id = a.id
|
||||
WHERE t.id = t_a_l.track_id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM release r
|
||||
INNER JOIN release_artist_link r_a_l
|
||||
ON r_a_l.artist_id = a.id
|
||||
WHERE r.id = r_a_l.release_id)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM artist_info ai
|
||||
|
||||
@@ -42,7 +42,8 @@ namespace lms::db
|
||||
for (std::string_view keyword : params.keywords)
|
||||
query.where("d.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeForLikeKeyword(keyword) + "%");
|
||||
|
||||
if (params.artist.isValid()
|
||||
if (params.trackArtist.isValid()
|
||||
|| params.releaseArtist.isValid()
|
||||
|| params.release.isValid()
|
||||
|| params.medium.isValid())
|
||||
{
|
||||
@@ -62,12 +63,24 @@ namespace lms::db
|
||||
if (params.release.isValid())
|
||||
query.where("t.release_id = ?").bind(params.release);
|
||||
|
||||
if (params.artist.isValid())
|
||||
if (params.releaseArtist.isValid())
|
||||
{
|
||||
assert(!params.trackArtist.isValid());
|
||||
|
||||
query.join("artist a ON a.id = r_a_l.artist_id")
|
||||
.join("release_artist_link r_a_l ON r_a_l.release_id = t.release_id")
|
||||
.where("a.id = ?")
|
||||
.bind(params.releaseArtist);
|
||||
}
|
||||
|
||||
if (params.trackArtist.isValid())
|
||||
{
|
||||
assert(!params.releaseArtist.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);
|
||||
.bind(params.trackArtist);
|
||||
|
||||
if (!params.trackArtistLinkTypes.empty())
|
||||
{
|
||||
|
||||
@@ -52,7 +52,8 @@ namespace lms::db
|
||||
if (params.filters.mediaLibrary.isValid()
|
||||
|| params.filters.codec.has_value()
|
||||
|| params.filters.label.isValid()
|
||||
|| params.filters.releaseType.isValid())
|
||||
|| params.filters.releaseType.isValid()
|
||||
|| params.trackArtistLinkType.has_value())
|
||||
{
|
||||
query.join("track t ON t.id = t_a_l.track_id");
|
||||
|
||||
@@ -75,8 +76,11 @@ namespace lms::db
|
||||
}
|
||||
}
|
||||
|
||||
if (params.linkType)
|
||||
query.where("t_a_l.type = ?").bind(*params.linkType);
|
||||
if (params.releaseArtistsOnly)
|
||||
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
|
||||
|
||||
if (params.trackArtistLinkType.has_value())
|
||||
query.where("t_a_l.type = ?").bind(params.trackArtistLinkType.value());
|
||||
|
||||
if (!params.filters.clusters.empty())
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
@@ -72,7 +73,7 @@ namespace lms::db
|
||||
|| params.sortMethod == ReleaseSortMethod::OriginalDateDesc
|
||||
|| params.writtenAfter.isValid()
|
||||
|| params.dateRange
|
||||
|| params.artist.isValid()
|
||||
|| params.trackArtist.isValid()
|
||||
|| params.filters.clusters.size() == 1
|
||||
|| params.filters.mediaLibrary.isValid()
|
||||
|| params.filters.codec.has_value()
|
||||
@@ -141,13 +142,22 @@ namespace lms::db
|
||||
.bind(SyncState::PendingRemove);
|
||||
}
|
||||
|
||||
if (params.artist.isValid()
|
||||
if (params.artist.isValid())
|
||||
{
|
||||
assert(!params.trackArtist.isValid());
|
||||
query.join("release_artist_link r_a_l ON r_a_l.release_id = r.id");
|
||||
query.where("r_a_l.artist_id = ?").bind(params.artist);
|
||||
}
|
||||
|
||||
if (params.trackArtist.isValid()
|
||||
|| params.sortMethod == ReleaseSortMethod::ArtistNameThenName)
|
||||
{
|
||||
assert(!params.artist.isValid());
|
||||
|
||||
query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id");
|
||||
|
||||
if (params.artist.isValid())
|
||||
query.where("t_a_l.artist_id = ?").bind(params.artist);
|
||||
if (params.trackArtist.isValid())
|
||||
query.where("t_a_l.artist_id = ?").bind(params.trackArtist);
|
||||
|
||||
if (params.sortMethod == ReleaseSortMethod::ArtistNameThenName)
|
||||
query.join("artist a ON a.id = t_a_l.artist_id");
|
||||
@@ -168,30 +178,6 @@ namespace lms::db
|
||||
}
|
||||
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 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 (t_a_l.artist_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.filters.clusters.size() == 1)
|
||||
@@ -272,7 +258,7 @@ namespace lms::db
|
||||
}
|
||||
|
||||
template<typename ResultType>
|
||||
Wt::Dbo::Query<ResultType> createArtistQuery(Wt::Dbo::Session& session, std::string_view itemToSelect, ReleaseId releaseId, TrackArtistLinkType linkType)
|
||||
Wt::Dbo::Query<ResultType> createTrackArtistQuery(Wt::Dbo::Session& session, std::string_view itemToSelect, ReleaseId releaseId, TrackArtistLinkType linkType)
|
||||
{
|
||||
auto query{ session.query<ResultType>("SELECT " + std::string{ itemToSelect } + " from artist a")
|
||||
.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id")
|
||||
@@ -700,19 +686,50 @@ namespace lms::db
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const
|
||||
std::vector<ObjectPtr<Artist>> Release::getArtists() const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
const auto query{ createArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), linkType) };
|
||||
auto query{ session()->query<Wt::Dbo::ptr<Artist>>("SELECT a from artist a") };
|
||||
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
|
||||
query.where("r_a_l.release_id = ?").bind(getId());
|
||||
query.groupBy("a.id");
|
||||
|
||||
return utils::fetchQueryResults<Artist::pointer>(query);
|
||||
}
|
||||
|
||||
std::vector<ArtistId> Release::getArtistIds(TrackArtistLinkType linkType) const
|
||||
bool Release::hasArtist(ArtistId artistId) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
const auto query{ createArtistQuery<ArtistId>(*session(), "a.id", getId(), linkType) };
|
||||
auto query{ session()->query<int>("SELECT COUNT(1) FROM release_artist_link r_a_l") };
|
||||
query.where("r_a_l.release_id = ?").bind(getId());
|
||||
query.where("r_a_l.artist_id = ?").bind(artistId);
|
||||
|
||||
return utils::fetchQuerySingleResult(query) > 0;
|
||||
}
|
||||
|
||||
std::vector<Artist::pointer> Release::getTrackArtists(TrackArtistLinkType type) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
const auto query{ createTrackArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), type) };
|
||||
return utils::fetchQueryResults<Artist::pointer>(query);
|
||||
}
|
||||
|
||||
void Release::visitTrackArtists(TrackArtistLinkType type, std::function<void(const ObjectPtr<Artist>&)> visitor) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
const auto query{ createTrackArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), type) };
|
||||
return utils::forEachQueryResult(query, visitor);
|
||||
}
|
||||
|
||||
std::vector<ArtistId> Release::getTrackArtistIds(TrackArtistLinkType linkType) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
const auto query{ createTrackArtistQuery<ArtistId>(*session(), "a.id", getId(), linkType) };
|
||||
return utils::fetchQueryResults(query);
|
||||
}
|
||||
|
||||
@@ -765,6 +782,36 @@ namespace lms::db
|
||||
return utils::fetchQueryResults<Medium::pointer>(query);
|
||||
}
|
||||
|
||||
std::vector<ObjectPtr<ReleaseArtistLink>> Release::getArtistLinks() const
|
||||
{
|
||||
return utils::fetchQueryResults<ReleaseArtistLink::pointer>(_releaseArtistLinks.find());
|
||||
}
|
||||
|
||||
void Release::visitArtistLinks(const std::function<void(const ReleaseArtistLink::pointer& artistLink)>& visitor) const
|
||||
{
|
||||
return utils::forEachQueryResult(_releaseArtistLinks.find(), visitor);
|
||||
}
|
||||
|
||||
void Release::visitTrackArtistLinks(TrackArtistLinkType linkType, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
|
||||
{
|
||||
auto query{ session()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l from track_artist_link t_a_l") };
|
||||
query.join("track t ON t.id = t_a_l.track_id");
|
||||
query.where("t.release_id = ?").bind(getId());
|
||||
query.where("t_a_l.type = ?").bind(linkType);
|
||||
|
||||
utils::forEachQueryResult(query, visitor);
|
||||
}
|
||||
|
||||
void Release::clearArtistLinks()
|
||||
{
|
||||
_releaseArtistLinks.clear();
|
||||
}
|
||||
|
||||
void Release::addArtistLink(const ObjectPtr<ReleaseArtistLink>& artistLink)
|
||||
{
|
||||
_releaseArtistLinks.insert(getDboPtr(artistLink));
|
||||
}
|
||||
|
||||
void Release::clearLabels()
|
||||
{
|
||||
_labels.clear();
|
||||
@@ -803,7 +850,7 @@ namespace lms::db
|
||||
bool Release::hasVariousArtists() const
|
||||
{
|
||||
// TODO optimize
|
||||
return getArtists().size() > 1;
|
||||
return getTrackArtists().size() > 1;
|
||||
}
|
||||
|
||||
std::size_t Release::getTrackCount() const
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
DBO_INSTANTIATE_TEMPLATES(lms::db::ReleaseArtistLink)
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
Wt::Dbo::Query<Wt::Dbo::ptr<ReleaseArtistLink>> createQuery(Session& session, const ReleaseArtistLink::FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l FROM release_artist_link r_a_l") };
|
||||
|
||||
if (params.release.isValid())
|
||||
query.where("r_a_l.release_id = ?").bind(params.release);
|
||||
|
||||
if (params.artist.isValid())
|
||||
query.where("r_a_l.artist_id = ?").bind(params.artist);
|
||||
|
||||
if (params.sortMethod == ReleaseArtistLinkSortMethod::OriginalDateDesc)
|
||||
query.join("track t ON t.release_id = r_a_l.release_id");
|
||||
|
||||
if (params.mbidMatched)
|
||||
query.where("r_a_l.artist_mbid_matched = ?").bind(*params.mbidMatched);
|
||||
|
||||
switch (params.sortMethod)
|
||||
{
|
||||
case ReleaseArtistLinkSortMethod::None:
|
||||
break;
|
||||
case ReleaseArtistLinkSortMethod::OriginalDateDesc:
|
||||
query.orderBy("COALESCE(t.original_date, t.date) DESC");
|
||||
break;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ReleaseArtistLink::ReleaseArtistLink(const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched)
|
||||
: _artistMBIDMatched{ artistMBIDMatched }
|
||||
, _release{ getDboPtr(release) }
|
||||
, _artist{ getDboPtr(artist) }
|
||||
{
|
||||
}
|
||||
|
||||
ReleaseArtistLink::pointer ReleaseArtistLink::create(Session& session, const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched)
|
||||
{
|
||||
session.checkWriteTransaction();
|
||||
return session.getDboSession()->add(std::make_unique<ReleaseArtistLink>(release, artist, artistMBIDMatched));
|
||||
}
|
||||
|
||||
std::size_t ReleaseArtistLink::getCount(Session& session)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_artist_link"));
|
||||
}
|
||||
|
||||
ReleaseArtistLink::pointer ReleaseArtistLink::find(Session& session, ReleaseArtistLinkId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l").where("r_a_l.id = ?").bind(id));
|
||||
}
|
||||
|
||||
void ReleaseArtistLink::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
|
||||
{
|
||||
auto query{ createQuery(session, params) };
|
||||
utils::forEachQueryRangeResult(query, params.range, func);
|
||||
}
|
||||
|
||||
void ReleaseArtistLink::findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const ReleaseArtistLink::pointer&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l") };
|
||||
query.join("artist a ON r_a_l.artist_id = a.id");
|
||||
query.where("r_a_l.artist_mbid_matched = FALSE");
|
||||
query.where("r_a_l.artist_name <> a.name");
|
||||
|
||||
utils::applyRange(query, range);
|
||||
utils::forEachQueryResult(query, [&](const ReleaseArtistLink::pointer& link) {
|
||||
func(link);
|
||||
});
|
||||
}
|
||||
|
||||
void ReleaseArtistLink::findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const ReleaseArtistLink::pointer&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l") };
|
||||
query.join("artist a ON r_a_l.artist_id = a.id");
|
||||
query.where("r_a_l.artist_mbid_matched = FALSE");
|
||||
if (!allowArtistMBIDFallback)
|
||||
{
|
||||
query.where("a.mbid <> ''");
|
||||
}
|
||||
else
|
||||
{
|
||||
query.where(R"(
|
||||
(a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid))
|
||||
OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))");
|
||||
}
|
||||
|
||||
utils::applyRange(query, range);
|
||||
utils::forEachQueryResult(query, [&](const ReleaseArtistLink::pointer& link) {
|
||||
func(link);
|
||||
});
|
||||
}
|
||||
|
||||
void ReleaseArtistLink::setArtist(ObjectPtr<Artist> artist)
|
||||
{
|
||||
_artist = getDboPtr(artist);
|
||||
}
|
||||
|
||||
void ReleaseArtistLink::setArtistName(std::string_view artistName)
|
||||
{
|
||||
_artistName.assign(artistName, 0, Artist::maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, artistName.size() > Artist::maxNameLength, "Artist link name too long, truncated to '" << _artistName << "'");
|
||||
}
|
||||
|
||||
void ReleaseArtistLink::setArtistSortName(std::string_view artistSortName)
|
||||
{
|
||||
_artistSortName.assign(artistSortName, 0, Artist::maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, artistSortName.size() > Artist::maxNameLength, "Artist link sort name too long, truncated to '" << _artistSortName << "'");
|
||||
}
|
||||
} // namespace lms::db
|
||||
@@ -728,6 +728,27 @@ namespace lms::db
|
||||
return utils::fetchQueryResults<TrackArtistLink::pointer>(_trackArtistLinks.find());
|
||||
}
|
||||
|
||||
void Track::visitArtistLinks(const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
|
||||
{
|
||||
utils::forEachQueryResult(_trackArtistLinks.find(), visitor);
|
||||
}
|
||||
|
||||
std::vector<ObjectPtr<TrackArtistLink>> Track::getArtistLinks(TrackArtistLinkType type) const
|
||||
{
|
||||
std::vector<ObjectPtr<TrackArtistLink>> links;
|
||||
visitArtistLinks(type, [&links](const ObjectPtr<TrackArtistLink>& artistLink) { links.push_back(artistLink); });
|
||||
return links;
|
||||
}
|
||||
|
||||
void Track::visitArtistLinks(TrackArtistLinkType type, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
|
||||
{
|
||||
auto query{ session()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l from track_artist_link t_a_l") };
|
||||
query.where("t_a_l.track_id = ?").bind(getId());
|
||||
query.where("t_a_l.type = ?").bind(type);
|
||||
|
||||
return utils::forEachQueryResult(query, visitor);
|
||||
}
|
||||
|
||||
std::vector<std::vector<Cluster::pointer>> Track::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
|
||||
{
|
||||
assert(self());
|
||||
|
||||
@@ -88,10 +88,12 @@ namespace lms::db
|
||||
{
|
||||
session.checkWriteTransaction();
|
||||
|
||||
TrackArtistLink::pointer res{ session.getDboSession()->add(std::make_unique<TrackArtistLink>(track, artist, type, subType, artistMBIDMatched)) };
|
||||
session.getDboSession()->flush();
|
||||
return session.getDboSession()->add(std::make_unique<TrackArtistLink>(track, artist, type, subType, artistMBIDMatched));
|
||||
}
|
||||
|
||||
return res;
|
||||
TrackArtistLink::pointer TrackArtistLink::create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched)
|
||||
{
|
||||
return create(session, track, artist, type, std::string_view{}, artistMBIDMatched);
|
||||
}
|
||||
|
||||
std::size_t TrackArtistLink::getCount(Session& session)
|
||||
@@ -101,11 +103,6 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_artist_link"));
|
||||
}
|
||||
|
||||
TrackArtistLink::pointer TrackArtistLink::create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched)
|
||||
{
|
||||
return create(session, track, artist, type, std::string_view{}, artistMBIDMatched);
|
||||
}
|
||||
|
||||
TrackArtistLink::pointer TrackArtistLink::find(Session& session, TrackArtistLinkId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
@@ -125,10 +122,10 @@ namespace lms::db
|
||||
});
|
||||
}
|
||||
|
||||
void TrackArtistLink::find(Session& session, const FindParameters& parameters, const std::function<void(const TrackArtistLink::pointer&)>& func)
|
||||
void TrackArtistLink::find(Session& session, const FindParameters& params, const std::function<void(const TrackArtistLink::pointer&)>& func)
|
||||
{
|
||||
auto query{ createQuery(session, parameters) };
|
||||
utils::forEachQueryRangeResult(query, parameters.range, func);
|
||||
auto query{ createQuery(session, params) };
|
||||
utils::forEachQueryRangeResult(query, params.range, func);
|
||||
}
|
||||
|
||||
core::EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId)
|
||||
|
||||
@@ -43,8 +43,6 @@ namespace lms::db
|
||||
return "performer";
|
||||
case TrackArtistLinkType::Producer:
|
||||
return "producer";
|
||||
case TrackArtistLinkType::ReleaseArtist:
|
||||
return "albumartist";
|
||||
case TrackArtistLinkType::Remixer:
|
||||
return "remixer";
|
||||
case TrackArtistLinkType::Writer:
|
||||
|
||||
@@ -62,15 +62,15 @@ namespace lms::db
|
||||
struct FindParameters
|
||||
{
|
||||
Filters filters;
|
||||
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field)
|
||||
std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
||||
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field)
|
||||
bool releaseArtistsOnly{}; // if set, only release artists
|
||||
std::optional<TrackArtistLinkType> trackArtistLinkType; // if set, only artists that have produced at least one track with this link type
|
||||
ArtistSortMethod sortMethod{ ArtistSortMethod::None };
|
||||
std::optional<Range> range;
|
||||
Wt::WDateTime writtenAfter;
|
||||
UserId starringUser; // only artists starred by this user
|
||||
std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend
|
||||
TrackId track; // artists involved in this track
|
||||
ReleaseId release; // artists involved in this release
|
||||
|
||||
FindParameters& setFilters(const Filters& _filters)
|
||||
{
|
||||
@@ -82,9 +82,14 @@ namespace lms::db
|
||||
keywords = _keywords;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType)
|
||||
FindParameters& setReleaseArtistsOnly(bool _releaseArtistsOnly)
|
||||
{
|
||||
linkType = _linkType;
|
||||
releaseArtistsOnly = _releaseArtistsOnly;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setTrackArtistLinkType(std::optional<TrackArtistLinkType> _trackArtistLinkType)
|
||||
{
|
||||
trackArtistLinkType = _trackArtistLinkType;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setSortMethod(ArtistSortMethod _sortMethod)
|
||||
@@ -113,11 +118,6 @@ namespace lms::db
|
||||
track = _track;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setRelease(ReleaseId _release)
|
||||
{
|
||||
release = _release;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
Artist() = default;
|
||||
|
||||
@@ -53,10 +53,11 @@ namespace lms::db
|
||||
{
|
||||
std::optional<Range> range;
|
||||
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords
|
||||
ArtistId artist; // only directoies that involve this artist
|
||||
ArtistId trackArtist; // only directories that involve this track artist
|
||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
||||
MediumId medium; // only directories that involve this medium
|
||||
ReleaseId release; // only directories that involve this release
|
||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
||||
ArtistId releaseArtist; // only directories that involve this release artist
|
||||
DirectoryId parentDirectory; // If set, directories that have this parent
|
||||
bool withNoTrack{}; // If set, directories that do not contain any track
|
||||
MediaLibraryId mediaLibrary; // If set, directories in this library
|
||||
@@ -72,12 +73,18 @@ namespace lms::db
|
||||
keywords = _keywords;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
|
||||
FindParameters& setTrackArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
|
||||
{
|
||||
artist = _artist;
|
||||
trackArtist = _artist;
|
||||
trackArtistLinkTypes = _trackArtistLinkTypes;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setReleaseArtist(ArtistId _artist)
|
||||
{
|
||||
releaseArtist = _artist;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FindParameters& setMedium(MediumId _medium)
|
||||
{
|
||||
medium = _medium;
|
||||
|
||||
@@ -124,11 +124,17 @@ namespace lms::db
|
||||
|
||||
struct ArtistStatsFindParameters : public StatsFindParameters
|
||||
{
|
||||
std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
||||
std::optional<TrackArtistLinkType> trackArtistLinkType; // if set, only artists that have produced at least one track with this link type
|
||||
bool releaseArtistsOnly{};
|
||||
|
||||
ArtistStatsFindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType)
|
||||
ArtistStatsFindParameters& setReleaseArtistsOnly(bool _releaseArtistsOnly)
|
||||
{
|
||||
linkType = _linkType;
|
||||
releaseArtistsOnly = _releaseArtistsOnly;
|
||||
return *this;
|
||||
}
|
||||
ArtistStatsFindParameters& setTrackArtistLinkType(std::optional<TrackArtistLinkType> _trackArtistLinkType)
|
||||
{
|
||||
trackArtistLinkType = _trackArtistLinkType;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,8 +56,10 @@ namespace lms::db
|
||||
class ClusterType;
|
||||
class Medium;
|
||||
class Release;
|
||||
class ReleaseArtistLink;
|
||||
class Session;
|
||||
class Track;
|
||||
class TrackArtistLink;
|
||||
class User;
|
||||
|
||||
class Country final : public Object<Country, CountryId>
|
||||
@@ -167,15 +169,15 @@ namespace lms::db
|
||||
std::optional<Range> range;
|
||||
Wt::WDateTime writtenAfter;
|
||||
std::optional<YearRange> dateRange;
|
||||
UserId starringUser; // only releases starred by this user
|
||||
std::optional<FeedbackBackend> feedbackBackend; // and for this backend
|
||||
ArtistId artist; // only releases that involved this user
|
||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
||||
core::EnumSet<TrackArtistLinkType> excludedTrackArtistLinkTypes; // but not for these link types
|
||||
std::string releaseType; // If set, releases that has this release type
|
||||
std::optional<core::UUID> releaseGroupMBID; // If set, releases that belong to this release group
|
||||
DirectoryId directory; // if set, releases in this directory (cannot be set with parent directory)
|
||||
DirectoryId parentDirectory; // if set, releases in this parent directory (cannot be set with directory)
|
||||
UserId starringUser; // only releases starred by this user
|
||||
std::optional<FeedbackBackend> feedbackBackend; // and for this backend
|
||||
ArtistId artist; // only releases by this release artist
|
||||
ArtistId trackArtist; // only releases that involved this track artist
|
||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types, if set
|
||||
std::string releaseType; // If set, releases that has this release type
|
||||
std::optional<core::UUID> releaseGroupMBID; // If set, releases that belong to this release group
|
||||
DirectoryId directory; // if set, releases in this directory (cannot be set with parent directory)
|
||||
DirectoryId parentDirectory; // if set, releases in this parent directory (cannot be set with directory)
|
||||
|
||||
FindParameters& setFilters(const Filters& _filters)
|
||||
{
|
||||
@@ -218,11 +220,15 @@ namespace lms::db
|
||||
feedbackBackend = _feedbackBackend;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}, core::EnumSet<TrackArtistLinkType> _excludedTrackArtistLinkTypes = {})
|
||||
FindParameters& setArtist(ArtistId _artist)
|
||||
{
|
||||
artist = _artist;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setTrackArtist(ArtistId _trackArtist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
|
||||
{
|
||||
trackArtist = _trackArtist;
|
||||
trackArtistLinkTypes = _trackArtistLinkTypes;
|
||||
excludedTrackArtistLinkTypes = _excludedTrackArtistLinkTypes;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setReleaseType(std::string_view _releaseType)
|
||||
@@ -305,6 +311,9 @@ namespace lms::db
|
||||
ObjectPtr<Artwork> getPreferredArtwork() const;
|
||||
ArtworkId getPreferredArtworkId() const;
|
||||
std::vector<ObjectPtr<Medium>> getMediums() const;
|
||||
std::vector<ObjectPtr<ReleaseArtistLink>> getArtistLinks() const;
|
||||
void visitArtistLinks(const std::function<void(const ObjectPtr<ReleaseArtistLink>& artistLink)>& visitor) const;
|
||||
void visitTrackArtistLinks(TrackArtistLinkType linkType, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const;
|
||||
|
||||
// Setters
|
||||
void setName(std::string_view name) { _name = name; }
|
||||
@@ -313,6 +322,8 @@ namespace lms::db
|
||||
void setGroupMBID(const std::optional<core::UUID>& mbid) { _groupMBID = mbid ? mbid->getAsString() : ""; }
|
||||
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
|
||||
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
|
||||
void clearArtistLinks();
|
||||
void addArtistLink(const ObjectPtr<ReleaseArtistLink>& artistLink);
|
||||
void setCompilation(bool value) { _isCompilation = value; }
|
||||
void clearLabels();
|
||||
void clearCountries();
|
||||
@@ -325,9 +336,11 @@ namespace lms::db
|
||||
void setPreferredArtwork(ObjectPtr<Artwork> artwork);
|
||||
|
||||
// Get the artists of this release
|
||||
std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
||||
std::vector<ArtistId> getArtistIds(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
||||
std::vector<ObjectPtr<Artist>> getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
|
||||
std::vector<ObjectPtr<Artist>> getArtists() const;
|
||||
bool hasArtist(ArtistId artistId) const;
|
||||
std::vector<ObjectPtr<Artist>> getTrackArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
||||
void visitTrackArtists(TrackArtistLinkType type, std::function<void(const ObjectPtr<Artist>&)> visitor) const;
|
||||
std::vector<ArtistId> getTrackArtistIds(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
||||
bool hasVariousArtists() const;
|
||||
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
|
||||
|
||||
@@ -345,6 +358,7 @@ namespace lms::db
|
||||
Wt::Dbo::field(a, _comment, "comment");
|
||||
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
||||
Wt::Dbo::hasMany(a, _releaseArtistLinks, Wt::Dbo::ManyToOne, "release");
|
||||
Wt::Dbo::belongsTo(a, _preferredArtwork, "preferred_artwork", Wt::Dbo::OnDeleteSetNull);
|
||||
Wt::Dbo::hasMany(a, _labels, Wt::Dbo::ManyToMany, "release_label", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _releaseTypes, Wt::Dbo::ManyToMany, "release_release_type", "", Wt::Dbo::OnDeleteCascade);
|
||||
@@ -372,6 +386,7 @@ namespace lms::db
|
||||
std::string _comment;
|
||||
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<ReleaseArtistLink>> _releaseArtistLinks;
|
||||
Wt::Dbo::ptr<Artwork> _preferredArtwork;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Label>> _labels;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<ReleaseType>> _releaseTypes;
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
#include "database/Object.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/ArtistId.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(ReleaseArtistLinkId)
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Artist;
|
||||
class Session;
|
||||
class Release;
|
||||
|
||||
class ReleaseArtistLink final : public Object<ReleaseArtistLink, ReleaseArtistLinkId>
|
||||
{
|
||||
public:
|
||||
struct FindParameters
|
||||
{
|
||||
std::optional<Range> range;
|
||||
ArtistId artist; // if set, links involved with this artist
|
||||
ReleaseId release; // if set, artists involved in this release
|
||||
std::optional<bool> mbidMatched;
|
||||
ReleaseArtistLinkSortMethod sortMethod{ ReleaseArtistLinkSortMethod::None };
|
||||
|
||||
FindParameters& setRange(std::optional<Range> _range)
|
||||
{
|
||||
range = _range;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setArtist(ArtistId _artist)
|
||||
{
|
||||
artist = _artist;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setRelease(ReleaseId _release)
|
||||
{
|
||||
release = _release;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setMBIDMatched(std::optional<bool> _mbidMatched)
|
||||
{
|
||||
mbidMatched = _mbidMatched;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setSortMethod(ReleaseArtistLinkSortMethod _method)
|
||||
{
|
||||
sortMethod = _method;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
ReleaseArtistLink() = default;
|
||||
ReleaseArtistLink(const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched);
|
||||
|
||||
static pointer find(Session& session, ReleaseArtistLinkId linkId);
|
||||
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
|
||||
static std::size_t getCount(Session& session);
|
||||
|
||||
static void findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const pointer&)>& func);
|
||||
static void findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const pointer&)>& func);
|
||||
|
||||
// accessors
|
||||
ObjectPtr<Release> getRelease() const { return _release; }
|
||||
ObjectPtr<Artist> getArtist() const { return _artist; }
|
||||
ArtistId getArtistId() const { return _artist.id(); }
|
||||
std::string_view getArtistName() const { return _artistName; }
|
||||
std::string_view getArtistSortName() const { return _artistSortName; }
|
||||
bool isArtistMBIDMatched() const { return _artistMBIDMatched; }
|
||||
|
||||
// setters
|
||||
void setArtist(ObjectPtr<Artist> artist);
|
||||
void setArtistName(std::string_view artistName);
|
||||
void setArtistSortName(std::string_view artistSortName);
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _artistName, "artist_name");
|
||||
Wt::Dbo::field(a, _artistSortName, "artist_sort_name");
|
||||
Wt::Dbo::field(a, _artistMBIDMatched, "artist_mbid_matched");
|
||||
|
||||
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
static pointer create(Session& session, const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched);
|
||||
|
||||
TrackArtistLinkType _type{ TrackArtistLinkType::Artist };
|
||||
std::string _subType;
|
||||
std::string _artistName; // as it was in the tags
|
||||
std::string _artistSortName; // as it was in the tags
|
||||
bool _artistMBIDMatched{};
|
||||
|
||||
Wt::Dbo::ptr<Release> _release;
|
||||
Wt::Dbo::ptr<Artist> _artist;
|
||||
};
|
||||
} // namespace lms::db
|
||||
@@ -307,6 +307,10 @@ namespace lms::db
|
||||
std::vector<ObjectPtr<Artist>> getArtists(core::EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
||||
std::vector<ArtistId> getArtistIds(core::EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
||||
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const;
|
||||
void visitArtistLinks(const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const;
|
||||
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks(TrackArtistLinkType type) const;
|
||||
void visitArtistLinks(TrackArtistLinkType type, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const;
|
||||
|
||||
ReleaseId getReleaseId() const { return _release.id(); }
|
||||
ObjectPtr<Release> getRelease() const { return _release; }
|
||||
MediumId getMediumId() const { return _medium.id(); }
|
||||
@@ -320,7 +324,7 @@ namespace lms::db
|
||||
ObjectPtr<Artwork> getPreferredMediaArtwork() const;
|
||||
ArtworkId getPreferredMediaArtworkId() const;
|
||||
|
||||
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypes, std::size_t size) const;
|
||||
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const;
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
|
||||
@@ -97,11 +97,9 @@ namespace lms::db
|
||||
TrackArtistLink(const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched);
|
||||
|
||||
static void find(Session& session, TrackId trackId, const std::function<void(const pointer&, const ObjectPtr<Artist>&)>& func);
|
||||
static void find(Session& session, const FindParameters& parameters, const std::function<void(const pointer&)>& func);
|
||||
static void find(Session& session, const FindParameters& params, const std::function<void(const pointer&)>& func);
|
||||
static pointer find(Session& session, TrackArtistLinkId linkId);
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched = false);
|
||||
static pointer create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched = false);
|
||||
static core::EnumSet<TrackArtistLinkType> findUsedTypes(Session& session, ArtistId _artist);
|
||||
static void findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const pointer&)>& func);
|
||||
static void findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const pointer&)>& func);
|
||||
@@ -109,6 +107,7 @@ namespace lms::db
|
||||
// accessors
|
||||
ObjectPtr<Track> getTrack() const { return _track; }
|
||||
ObjectPtr<Artist> getArtist() const { return _artist; }
|
||||
ArtistId getArtistId() const { return _artist.id(); }
|
||||
TrackArtistLinkType getType() const { return _type; }
|
||||
std::string_view getSubType() const { return _subType; }
|
||||
std::string_view getArtistName() const { return _artistName; }
|
||||
@@ -134,6 +133,10 @@ namespace lms::db
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
static pointer create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched = false);
|
||||
static pointer create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched = false);
|
||||
|
||||
TrackArtistLinkType _type{ TrackArtistLinkType::Artist };
|
||||
std::string _subType;
|
||||
std::string _artistName; // as it was in the tags
|
||||
|
||||
@@ -86,6 +86,12 @@ namespace lms::db
|
||||
PubDateDesc,
|
||||
};
|
||||
|
||||
enum class ReleaseArtistLinkSortMethod
|
||||
{
|
||||
None,
|
||||
OriginalDateDesc,
|
||||
};
|
||||
|
||||
enum class ReleaseSortMethod
|
||||
{
|
||||
None,
|
||||
@@ -165,7 +171,7 @@ namespace lms::db
|
||||
Mixer = 5,
|
||||
Performer = 6,
|
||||
Producer = 7,
|
||||
ReleaseArtist = 8,
|
||||
// ReleaseArtist = 8,
|
||||
Remixer = 9,
|
||||
Writer = 10,
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Image.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
@@ -98,10 +99,10 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track2a.get().modify()->setMediaLibrary(library.get());
|
||||
track2b.get().modify()->setMediaLibrary(library.get());
|
||||
TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2a.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2b.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track3.get(), artist3.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track2a.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track2b.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track3.get(), artist3.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -243,7 +244,7 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
track.get().modify()->setName("MyTrackName");
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -258,13 +259,39 @@ namespace lms::db::tests
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
|
||||
{
|
||||
bool visited{};
|
||||
track->visitArtistLinks([&](const db::TrackArtistLink::pointer& artistLink) {
|
||||
visited = true;
|
||||
EXPECT_EQ(artistLink->getArtistId(), artist.getId());
|
||||
});
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
bool visited{};
|
||||
track->visitArtistLinks(TrackArtistLinkType::Artist, [&](const db::TrackArtistLink::pointer& artistLink) {
|
||||
visited = true;
|
||||
EXPECT_EQ(artistLink->getArtistId(), artist.getId());
|
||||
});
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
bool visited{};
|
||||
track->visitArtistLinks(TrackArtistLinkType::Conductor, [&](const db::TrackArtistLink::pointer&) {
|
||||
visited = true;
|
||||
});
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
|
||||
ASSERT_EQ(track->getArtistLinks().size(), 1);
|
||||
auto artistLink{ track->getArtistLinks().front() };
|
||||
EXPECT_EQ(artistLink->getTrack()->getId(), track.getId());
|
||||
EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId());
|
||||
|
||||
ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1);
|
||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0);
|
||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Composer }).size(), 0);
|
||||
EXPECT_EQ(track->getArtists({}).size(), 1);
|
||||
}
|
||||
|
||||
@@ -276,7 +303,7 @@ namespace lms::db::tests
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
|
||||
ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1);
|
||||
EXPECT_EQ(track->getArtistIds({ TrackArtistLinkType::ReleaseArtist }).size(), 0);
|
||||
EXPECT_EQ(track->getArtistIds({ TrackArtistLinkType::Composer }).size(), 0);
|
||||
EXPECT_EQ(track->getArtistIds({}).size(), 1);
|
||||
}
|
||||
|
||||
@@ -302,6 +329,32 @@ namespace lms::db::tests
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_singleRelease)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedArtist artist{ session, "MyArtist" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results, std::vector<db::ArtistId>{ artist.getId() });
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
session.create<ReleaseArtistLink>(release.get(), artist.get(), false);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_singleTrack_mediaLibrary)
|
||||
@@ -315,7 +368,7 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
track.get().modify()->setName("MyTrackName");
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
track.get().modify()->setMediaLibrary(library.get());
|
||||
}
|
||||
{
|
||||
@@ -344,9 +397,9 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Writer);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Producer);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Writer);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -357,10 +410,12 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Artist)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::ReleaseArtist)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Writer)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Composer)).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Artist)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Producer)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Writer)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Composer)).results.size(), 0);
|
||||
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -370,7 +425,7 @@ namespace lms::db::tests
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
|
||||
artists = track->getArtists({ TrackArtistLinkType::ReleaseArtist });
|
||||
artists = track->getArtists({ TrackArtistLinkType::Producer });
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
|
||||
@@ -379,7 +434,7 @@ namespace lms::db::tests
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId())) };
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Producer }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
@@ -393,7 +448,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
core::EnumSet<TrackArtistLinkType> types{ TrackArtistLink::findUsedTypes(session, artist.getId()) };
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::ReleaseArtist));
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Producer));
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist));
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Writer));
|
||||
EXPECT_FALSE(types.contains(TrackArtistLinkType::Composer));
|
||||
@@ -416,7 +471,7 @@ namespace lms::db::tests
|
||||
};
|
||||
|
||||
EXPECT_TRUE(containsType(TrackArtistLinkType::Artist));
|
||||
EXPECT_TRUE(containsType(TrackArtistLinkType::ReleaseArtist));
|
||||
EXPECT_TRUE(containsType(TrackArtistLinkType::Producer));
|
||||
EXPECT_TRUE(containsType(TrackArtistLinkType::Writer));
|
||||
}
|
||||
}
|
||||
@@ -431,8 +486,8 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -450,7 +505,7 @@ namespace lms::db::tests
|
||||
|
||||
EXPECT_EQ(track->getArtists({}).size(), 2);
|
||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2);
|
||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0);
|
||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Arranger }).size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 2);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2);
|
||||
}
|
||||
@@ -466,13 +521,13 @@ namespace lms::db::tests
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Arranger }));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Arranger }));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Artist }));
|
||||
@@ -518,6 +573,27 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_findByReleaseArtistLinks)
|
||||
{
|
||||
ScopedRelease release{ session, "release" };
|
||||
ScopedArtist artist{ session, "artist" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
session.create<ReleaseArtistLink>(release.get(), artist.get(), false);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_findByCodec)
|
||||
{
|
||||
ScopedArtist artist1{ session, "A" };
|
||||
@@ -527,8 +603,8 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
|
||||
track1.get().modify()->setCodec(core::media::Codec::MP3);
|
||||
track2.get().modify()->setCodec(core::media::Codec::FLAC);
|
||||
@@ -554,7 +630,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
artist.get().modify()->setSortName("ZZZ");
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -768,8 +844,8 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track1.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track2.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
|
||||
track1.get().modify()->setRelease(release.get());
|
||||
}
|
||||
@@ -783,51 +859,6 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_findByRelease)
|
||||
{
|
||||
ScopedArtist artist{ session, "artist" };
|
||||
ScopedTrack track1{ session };
|
||||
ScopedTrack track2{ session };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track1.get().modify()->setRelease(release.get());
|
||||
track2.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const std::size_t count{ Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
||||
EXPECT_EQ(count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_artwork)
|
||||
{
|
||||
ScopedImage image{ session, "/image1.jpg" };
|
||||
@@ -875,11 +906,11 @@ namespace lms::db::tests
|
||||
trackD1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 3 } });
|
||||
trackA2.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 4 } });
|
||||
|
||||
TrackArtistLink::create(session, trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
|
||||
TrackArtistLink::create(session, trackB1.get(), artistB.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, trackC1.get(), artistC.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, trackD1.get(), artistD.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
|
||||
session.create<TrackArtistLink>(trackB1.get(), artistB.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(trackC1.get(), artistC.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(trackD1.get(), artistD.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -915,11 +946,11 @@ namespace lms::db::tests
|
||||
trackD1.get().modify()->setLastWriteTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 3 } });
|
||||
trackA2.get().modify()->setLastWriteTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 4 } });
|
||||
|
||||
TrackArtistLink::create(session, trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
|
||||
TrackArtistLink::create(session, trackB1.get(), artistB.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, trackC1.get(), artistC.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, trackD1.get(), artistD.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
|
||||
session.create<TrackArtistLink>(trackB1.get(), artistB.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(trackC1.get(), artistC.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(trackD1.get(), artistD.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -976,16 +1007,16 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
{
|
||||
auto link{ TrackArtistLink::create(session, trackA1.get(), artistA.get(), TrackArtistLinkType::Artist, true) };
|
||||
auto link{ session.create<TrackArtistLink>(trackA1.get(), artistA.get(), TrackArtistLinkType::Artist, true) };
|
||||
link.modify()->setArtistName("ArtistA");
|
||||
}
|
||||
{
|
||||
auto link{ TrackArtistLink::create(session, trackA2.get(), artistA.get(), TrackArtistLinkType::Artist, true) };
|
||||
auto link{ session.create<TrackArtistLink>(trackA2.get(), artistA.get(), TrackArtistLinkType::Artist, true) };
|
||||
link.modify()->setArtistName("AlternateArtistA");
|
||||
}
|
||||
|
||||
{
|
||||
auto link{ TrackArtistLink::create(session, trackB1.get(), artistB.get(), TrackArtistLinkType::Artist, true) };
|
||||
auto link{ session.create<TrackArtistLink>(trackB1.get(), artistB.get(), TrackArtistLinkType::Artist, true) };
|
||||
link.modify()->setArtistName("ArtistB");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ add_executable(test-database
|
||||
RatedRelease.cpp
|
||||
RatedTrack.cpp
|
||||
Release.cpp
|
||||
ReleaseArtistLink.cpp
|
||||
ScanSettings.cpp
|
||||
StarredArtist.cpp
|
||||
StarredRelease.cpp
|
||||
|
||||
@@ -445,7 +445,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||
auto trackArtistLink{ session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||
cluster1.get().modify()->addTrack(track.get());
|
||||
}
|
||||
|
||||
@@ -505,8 +505,8 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Mixer);
|
||||
cluster.get().modify()->addTrack(track.get());
|
||||
}
|
||||
|
||||
@@ -544,7 +544,7 @@ namespace lms::db::tests
|
||||
tracks.emplace_back(session);
|
||||
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
|
||||
for (auto& cluster : clusters)
|
||||
cluster.get().modify()->addTrack(tracks.back().get());
|
||||
@@ -654,7 +654,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
track.get().modify()->setRelease(release.get());
|
||||
cluster.get().modify()->addTrack(track.get());
|
||||
}
|
||||
@@ -675,11 +675,11 @@ namespace lms::db::tests
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster.getId() })));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId()).setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster.getId() })));
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
}
|
||||
@@ -697,7 +697,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||
auto trackArtistLink{ session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
cluster1.get().modify()->addTrack(track.get());
|
||||
cluster2.get().modify()->addTrack(track.get());
|
||||
@@ -706,11 +706,11 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId(), cluster2.getId() })));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId()).setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId(), cluster2.getId() })));
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
}
|
||||
@@ -822,10 +822,10 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
if (i < 5)
|
||||
TrackArtistLink::create(session, tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
else
|
||||
{
|
||||
TrackArtistLink::create(session, tracks.back().get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(tracks.back().get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
cluster2.get().modify()->addTrack(tracks.back().get());
|
||||
}
|
||||
|
||||
@@ -835,7 +835,7 @@ namespace lms::db::tests
|
||||
tracks.emplace_back(session);
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist);
|
||||
cluster2.get().modify()->addTrack(tracks.back().get());
|
||||
}
|
||||
|
||||
@@ -855,12 +855,12 @@ namespace lms::db::tests
|
||||
}
|
||||
|
||||
{
|
||||
auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::ReleaseArtist }) };
|
||||
auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Lyricist }) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }) };
|
||||
auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Artist, TrackArtistLinkType::Lyricist }) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist2.getId());
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace lms::db::tests
|
||||
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
tracks.back().get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
|
||||
@@ -67,18 +67,18 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||
auto trackArtistLink{ session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
|
||||
auto artists{ release->getArtists() };
|
||||
auto artists{ release->getTrackArtists() };
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
ASSERT_EQ(artists.front()->getId(), artist.getId());
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "database/objects/Listen.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
@@ -170,7 +171,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track2.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track2.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -200,7 +201,7 @@ namespace lms::db::tests
|
||||
Listen::ArtistStatsFindParameters params;
|
||||
params.setUser(user->getId());
|
||||
params.setScrobblingBackend(ScrobblingBackend::ListenBrainz);
|
||||
params.setLinkType(TrackArtistLinkType::Producer);
|
||||
params.setTrackArtistLinkType(TrackArtistLinkType::Producer);
|
||||
|
||||
auto artists{ Listen::getTopArtists(session, params) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
@@ -235,8 +236,8 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -304,7 +305,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -362,7 +363,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
track.get().modify()->setMediaLibrary(library.get());
|
||||
}
|
||||
|
||||
@@ -695,7 +696,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -859,7 +860,50 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Listen_getRecentArtists)
|
||||
TEST_F(DatabaseFixture, Listen_getRecentReleaseArtists)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
ScopedUser user{ session, "MyUser" };
|
||||
ScopedArtist artist{ session, "MyArtist" };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<ReleaseArtistLink>(release.get(), artist.get(), false);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Listen::ArtistStatsFindParameters params;
|
||||
params.setUser(user->getId());
|
||||
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
||||
params.setReleaseArtistsOnly(true);
|
||||
|
||||
auto artists{ Listen::getRecentArtists(session, params) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
EXPECT_EQ(artists.moreResults, false);
|
||||
}
|
||||
|
||||
const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } };
|
||||
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Listen::ArtistStatsFindParameters params;
|
||||
params.setUser(user->getId());
|
||||
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
||||
params.setReleaseArtistsOnly(true);
|
||||
|
||||
auto artists{ Listen::getRecentArtists(session, params) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results[0], artist->getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Listen_getRecentTrackArtists)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
ScopedUser user{ session, "MyUser" };
|
||||
@@ -867,7 +911,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -896,6 +940,20 @@ namespace lms::db::tests
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results[0], artist->getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Listen::ArtistStatsFindParameters params;
|
||||
params.setUser(user->getId());
|
||||
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
||||
params.setReleaseArtistsOnly(true);
|
||||
|
||||
auto artists{ Listen::getRecentArtists(session, params) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
EXPECT_EQ(artists.moreResults, false);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
@@ -912,7 +970,7 @@ namespace lms::db::tests
|
||||
Listen::ArtistStatsFindParameters params;
|
||||
params.setUser(user->getId());
|
||||
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
||||
params.setLinkType(TrackArtistLinkType::Producer);
|
||||
params.setTrackArtistLinkType(TrackArtistLinkType::Producer);
|
||||
|
||||
auto artists{ Listen::getRecentArtists(session, params) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
@@ -935,7 +993,7 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Listen_getRecentArtists_multi)
|
||||
TEST_F(DatabaseFixture, Listen_getRecentTrackArtists_multi)
|
||||
{
|
||||
ScopedUser user{ session, "MyUser" };
|
||||
ScopedTrack track1{ session };
|
||||
@@ -946,8 +1004,8 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1014,7 +1072,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1056,7 +1114,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
track.get().modify()->setMediaLibrary(library.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "database/objects/RatedArtist.hpp"
|
||||
#include "database/objects/RatedRelease.hpp"
|
||||
#include "database/objects/RatedTrack.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/ScanSettings.hpp"
|
||||
#include "database/objects/StarredArtist.hpp"
|
||||
#include "database/objects/StarredRelease.hpp"
|
||||
@@ -368,6 +369,7 @@ VALUES
|
||||
EXPECT_FALSE(RatedRelease::find(session, RatedReleaseId{}));
|
||||
EXPECT_FALSE(RatedTrack::find(session, RatedTrackId{}));
|
||||
EXPECT_FALSE(Release::find(session, ReleaseId{}));
|
||||
EXPECT_FALSE(ReleaseArtistLink::find(session, ReleaseArtistLinkId{}));
|
||||
EXPECT_FALSE(ReleaseType::find(session, ReleaseTypeId{}));
|
||||
EXPECT_FALSE(ScanSettings::find(session, ScanSettingsId{}));
|
||||
EXPECT_FALSE(StarredArtist::find(session, StarredArtistId{}));
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Image.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
@@ -689,7 +691,7 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Release_artist)
|
||||
TEST_F(DatabaseFixture, Release_trackArtist)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedTrack track{ session };
|
||||
@@ -703,21 +705,21 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })) };
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist })) };
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist })), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId())), 0);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist }));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist2.getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist2.getId())), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist2.getId(), { TrackArtistLinkType::Artist })), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist2.getId())), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Producer);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Producer);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -725,83 +727,124 @@ namespace lms::db::tests
|
||||
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1);
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })) };
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist })) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })), 1);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Remixer })), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist })), 1);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Remixer })), 0);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer }));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer }));
|
||||
EXPECT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer })), 1);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer })), 1);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist }));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist2.getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId()));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist2.getId()));
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist, TrackArtistLinkType::Artist }));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Writer, TrackArtistLinkType::Artist }));
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId()));
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 1);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId())), 1);
|
||||
EXPECT_EQ(release->getTrackArtists().size(), 1);
|
||||
EXPECT_EQ(release->getTrackArtists(TrackArtistLinkType::Artist).size(), 1);
|
||||
EXPECT_EQ(release->getTrackArtists(TrackArtistLinkType::Conductor).size(), 0);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer }));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Composer }));
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer, TrackArtistLinkType::Mixer }));
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), {}, { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), {}, { TrackArtistLinkType::Artist, TrackArtistLinkType::Composer }));
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Composer, TrackArtistLinkType::Mixer }));
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Release_releaseArtist)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedTrack track{ session };
|
||||
ScopedArtist artist{ session, "MyArtist" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })) };
|
||||
{
|
||||
bool visited{};
|
||||
release->visitTrackArtistLinks(TrackArtistLinkType::Artist, [&](const db::TrackArtistLink::pointer& link) {
|
||||
visited = true;
|
||||
EXPECT_EQ(link->getArtistId(), artist.getId());
|
||||
});
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
bool visited{};
|
||||
release->visitTrackArtistLinks(TrackArtistLinkType::Conductor, [&](const db::TrackArtistLink::pointer&) {
|
||||
visited = true;
|
||||
});
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Release_artist)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedArtist artist1{ session, "MyArtist" };
|
||||
ScopedArtist artist2{ session, "MyArtist" };
|
||||
ScopedArtist artist3{ session, "MyArtist" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist1.getId())) };
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 0);
|
||||
EXPECT_EQ(release->getArtists(TrackArtistLinkType::ReleaseArtist).size(), 0);
|
||||
EXPECT_EQ(release->getArtistIds(TrackArtistLinkType::ReleaseArtist).size(), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist1.getId())), 0);
|
||||
EXPECT_EQ(release->getTrackArtists(TrackArtistLinkType::Conductor).size(), 0);
|
||||
EXPECT_EQ(release->getArtistLinks().size(), 0);
|
||||
|
||||
bool visited{};
|
||||
release->visitArtistLinks([&](const db::ReleaseArtistLink::pointer&) {
|
||||
visited = true;
|
||||
});
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
|
||||
auto link1{ session.create<ReleaseArtistLink>(release.get(), artist1.get(), false) };
|
||||
auto link2{ session.create<ReleaseArtistLink>(release.get(), artist2.get(), true) };
|
||||
link2.modify()->setArtistName("MyArtistAlternateName");
|
||||
link2.modify()->setArtistSortName("MyArtistAlternateSortName");
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ release->getArtists(TrackArtistLinkType::ReleaseArtist) };
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
const auto links{ release->getArtistLinks() };
|
||||
ASSERT_EQ(links.size(), 2);
|
||||
EXPECT_EQ(links[0]->getArtistId(), artist1.getId());
|
||||
EXPECT_EQ(links[0]->getArtistName(), "");
|
||||
EXPECT_EQ(links[0]->getArtistSortName(), "");
|
||||
EXPECT_EQ(links[1]->getArtistId(), artist2.getId());
|
||||
EXPECT_EQ(links[1]->getArtistName(), "MyArtistAlternateName");
|
||||
EXPECT_EQ(links[1]->getArtistSortName(), "MyArtistAlternateSortName");
|
||||
EXPECT_EQ(links[1]->isArtistMBIDMatched(), true);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ release->getArtistIds(TrackArtistLinkType::ReleaseArtist) };
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
std::vector<db::ReleaseArtistLink::pointer> links;
|
||||
release->visitArtistLinks([&](const db::ReleaseArtistLink::pointer& artistLink) {
|
||||
links.push_back(artistLink);
|
||||
});
|
||||
ASSERT_EQ(links.size(), 2);
|
||||
EXPECT_EQ(links[0]->getArtistId(), artist1.getId());
|
||||
EXPECT_EQ(links[0]->getArtistName(), "");
|
||||
EXPECT_EQ(links[0]->getArtistSortName(), "");
|
||||
EXPECT_EQ(links[1]->getArtistId(), artist2.getId());
|
||||
EXPECT_EQ(links[1]->getArtistName(), "MyArtistAlternateName");
|
||||
EXPECT_EQ(links[1]->getArtistSortName(), "MyArtistAlternateSortName");
|
||||
EXPECT_EQ(links[1]->isArtistMBIDMatched(), true);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -809,13 +852,31 @@ namespace lms::db::tests
|
||||
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1);
|
||||
|
||||
const auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })) };
|
||||
const auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist1.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })), 1);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 1);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist1.getId())), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist1.getId())), 1);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist2.getId())), 0);
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist2.getId())), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto artists{ release->getArtists() };
|
||||
EXPECT_EQ(artists.size(), 2);
|
||||
EXPECT_EQ(artists[0]->getId(), artist1.getId());
|
||||
EXPECT_EQ(artists[1]->getId(), artist2.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_TRUE(release->hasArtist(artist1.getId()));
|
||||
EXPECT_FALSE(release->hasArtist(artist3.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Release_isCompilation)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 "Common.hpp"
|
||||
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
TEST_F(DatabaseFixture, ReleaseArtistLink)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedTrack track{ session };
|
||||
ScopedArtist artist{ session, "MyArtist" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
ReleaseArtistLink::FindParameters params;
|
||||
params.setRelease(release.getId());
|
||||
|
||||
bool visited{};
|
||||
ReleaseArtistLink::find(session, params, [&](const ReleaseArtistLink::pointer&) {
|
||||
visited = true;
|
||||
});
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
track.get().modify()->setRelease(release.get());
|
||||
session.create<ReleaseArtistLink>(release.get(), artist.get(), false);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
ReleaseArtistLink::FindParameters params;
|
||||
params.setRelease(release.getId());
|
||||
params.setMBIDMatched(false);
|
||||
|
||||
bool visited{};
|
||||
ReleaseArtistLink::find(session, params, [&](const ReleaseArtistLink::pointer& link) {
|
||||
visited = true;
|
||||
EXPECT_EQ(link->getArtistId(), artist.getId());
|
||||
});
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
Reference in New Issue
Block a user