diff --git a/src/libs/services/database/impl/Artist.cpp b/src/libs/services/database/impl/Artist.cpp index 575cb675..1243d043 100644 --- a/src/libs/services/database/impl/Artist.cpp +++ b/src/libs/services/database/impl/Artist.cpp @@ -204,114 +204,6 @@ Artist::find(Session& session, const FindParameters& params) return execQuery(query, params.range); } -RangeResults -Artist::getReleases(Range range, const std::vector& clusterIds) const -{ - assert(session()); - - WhereClause where; - - std::ostringstream oss; - - oss << "SELECT DISTINCT r.id FROM release r INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id INNER JOIN track t ON t.release_id = r.id"; - - if (!clusterIds.empty()) - { - oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; - - WhereClause clusterClause; - - for (auto id : clusterIds) - clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString()); - - where.And(clusterClause); - } - - where.And(WhereClause("a.id = ?")).bind(getId().toString()); - - oss << " " << where.get(); - - if (!clusterIds.empty()) - oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size(); - - oss << " ORDER BY t.date DESC, r.name COLLATE NOCASE"; - - auto query {session()->query(oss.str())}; - - for (const std::string& bindArg : where.getBindArgs()) - query.bind(bindArg); - - return execQuery(query, range); -} - -std::size_t -Artist::getReleaseCount() const -{ - assert(session()); - - int res = session()->query("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id INNER JOIN track t ON t.release_id = r.id") - .where("a.id = ?").bind(getId()); - return res; -} - -std::vector -Artist::getTracks(std::optional linkType) const -{ - assert(session()); - - auto query {session()->query>("SELECT DISTINCT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id") - .where("a.id = ?").bind(getId()) - .orderBy("t.date DESC,t.release_id,t.disc_number,t.track_number")}; - - if (linkType) - query.where("t_a_l.type = ?").bind(*linkType); - - auto tracks {query.resultList()}; - return std::vector(tracks.begin(), tracks.end()); -} - -RangeResults -Artist::getNonReleaseTracks(std::optional linkType, Range range) const -{ - assert(session()); - - auto query {session()->query>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id") - .where("a.id = ?").bind(getId()) - .where("t.release_id is NULL") - .orderBy("t.name")}; - if (linkType) - query.where("t_a_l.type = ?").bind(*linkType); - - return execQuery(query, range); -} - -bool -Artist::hasNonReleaseTracks(std::optional linkType) const -{ - auto query {session()->query>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id") - .where("a.id = ?").bind(getId()) - .where("t.release_id is NULL") - .orderBy("t.name")}; - - if (linkType) - query.where("t_a_l.type = ?").bind(*linkType); - - return !query.resultList().empty(); -} - -std::vector -Artist::getRandomTracks(std::optional count) const -{ - assert(session()); - - Wt::Dbo::collection> tracks {session()->query>("SELECT t from track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id") - .where("a.id = ?").bind(getId()) - .orderBy("RANDOM()") - .limit(count ? static_cast(*count) : -1)}; - - return std::vector(tracks.begin(), tracks.end()); -} - RangeResults Artist::findSimilarArtists(EnumSet artistLinkTypes, Range range) const { diff --git a/src/libs/services/database/impl/Migration.cpp b/src/libs/services/database/impl/Migration.cpp index b83ebee2..fed5d7fc 100644 --- a/src/libs/services/database/impl/Migration.cpp +++ b/src/libs/services/database/impl/Migration.cpp @@ -549,6 +549,28 @@ CREATE TABLE "listen" ( ScanSettings::get(session).modify()->addAudioFileExtension(".wv"); } + static + void + migrateFromV33(Session& session) + { + // remove name from track_artist_link + // Drop Auth mode + session.getDboSession().execute(R"( +CREATE TABLE IF NOT EXISTS "track_artist_link_backup" ( + "id" integer primary key autoincrement, + "version" integer not null, + "type" integer not null, + "track_id" bigint, + "artist_id" bigint, + constraint "fk_track_artist_link_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred, + constraint "fk_track_artist_link_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred +); +))"); + session.getDboSession().execute("INSERT INTO track_artist_link_backup SELECT id, version, type, track_id, artist_id FROM track_artist_link"); + session.getDboSession().execute("DROP TABLE track_artist_link"); + session.getDboSession().execute("ALTER TABLE track_artist_link_backup RENAME TO track_artist_link"); + } + void doDbMigration(Session& session) { @@ -588,6 +610,7 @@ CREATE TABLE "listen" ( {30, migrateFromV30}, {31, migrateFromV31}, {32, migrateFromV32}, + {33, migrateFromV33}, }; while (1) diff --git a/src/libs/services/database/impl/Migration.hpp b/src/libs/services/database/impl/Migration.hpp index 9b4f1eed..108c8c6c 100644 --- a/src/libs/services/database/impl/Migration.hpp +++ b/src/libs/services/database/impl/Migration.hpp @@ -26,7 +26,7 @@ namespace Database class Session; using Version = std::size_t; - static constexpr Version LMS_DATABASE_VERSION {33}; + static constexpr Version LMS_DATABASE_VERSION {34}; class VersionInfo { public: diff --git a/src/libs/services/database/impl/Track.cpp b/src/libs/services/database/impl/Track.cpp index 6c1c2a94..e5f28a5e 100644 --- a/src/libs/services/database/impl/Track.cpp +++ b/src/libs/services/database/impl/Track.cpp @@ -43,7 +43,7 @@ createQuery(Session& session, const Track::FindParameters& params) { session.checkSharedLocked(); - auto query {session.getDboSession().query("SELECT t.id from track t")}; + auto query {session.getDboSession().query("SELECT DISTINCT t.id from track t")}; for (std::string_view keyword : params.keywords) query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + escapeLikeKeyword(keyword) + "%"); @@ -79,6 +79,33 @@ createQuery(Session& session, const Track::FindParameters& params) query.where(oss.str()); } + if (params.artist.isValid()) + { + query.join("artist a ON a.id = t_a_l.artist_id") + .join("track_artist_link t_a_l ON t_a_l.track_id = t.id") + .where("a.id = ?").bind(params.artist); + + if (!params.trackArtistLinkTypes.empty()) + { + std::ostringstream oss; + + bool first {true}; + for (TrackArtistLinkType linkType : params.trackArtistLinkTypes) + { + if (!first) + oss << " OR "; + oss << "t_a_l.type = ?"; + query.bind(linkType); + + first = false; + } + query.where(oss.str()); + } + } + + if (params.nonRelease) + query.where("t.release_id IS NULL"); + switch (params.sortMethod) { case TrackSortMethod::None: @@ -93,6 +120,12 @@ createQuery(Session& session, const Track::FindParameters& params) assert(params.starringUser.isValid()); query.orderBy("s_t.date_time DESC"); break; + case TrackSortMethod::Name: + query.orderBy("t.name COLLATE NOCASE"); + break; + case TrackSortMethod::DateDescAndRelease: + query.orderBy("t.date DESC,t.release_id,t.disc_number,t.track_number"); + break; } return query; diff --git a/src/libs/services/database/include/services/database/Artist.hpp b/src/libs/services/database/include/services/database/Artist.hpp index 8fd4b1a9..23b153af 100644 --- a/src/libs/services/database/include/services/database/Artist.hpp +++ b/src/libs/services/database/include/services/database/Artist.hpp @@ -89,13 +89,6 @@ class Artist : public Object const std::string& getSortName() const { return _sortName; } std::optional getMBID() const { return UUID::fromString(_MBID); } - RangeResults getReleases(Range range, const std::vector& clusterIds = {}) const; // if non empty, get the releases that match all these clusters - std::size_t getReleaseCount() const; - std::vector> getTracks(std::optional linkType = {}) const; - bool hasNonReleaseTracks(std::optional linkType = std::nullopt) const; - RangeResults> getNonReleaseTracks(std::optional linkType, Range range) const; - std::vector> getRandomTracks(std::optional count) const; - // No artistLinkTypes means get them all RangeResults findSimilarArtists(EnumSet artistLinkTypes = {}, Range range = {}) const; diff --git a/src/libs/services/database/include/services/database/Track.hpp b/src/libs/services/database/include/services/database/Track.hpp index cc530921..997cdfd7 100644 --- a/src/libs/services/database/include/services/database/Track.hpp +++ b/src/libs/services/database/include/services/database/Track.hpp @@ -63,7 +63,10 @@ class Track : public Object Range range; Wt::WDateTime writtenAfter; UserId starringUser; // only tracks starred by this user - std::optional scrobbler; // and for this scrobbler + std::optional scrobbler; // and for this scrobbler + ArtistId artist; // only tracks that involve this user + EnumSet trackArtistLinkTypes; // and for these link types + bool nonRelease {}; // only tracks that do not belong to a release FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } @@ -71,7 +74,10 @@ class Track : public Object FindParameters& setRange(Range _range) { range = _range; return *this; } FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } FindParameters& setStarringUser(UserId _user, Scrobbler _scrobbler) { starringUser = _user; scrobbler = _scrobbler; return *this; } + FindParameters& setArtist(ArtistId _artist, EnumSet _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } + FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; } }; + struct PathResult { TrackId trackId; diff --git a/src/libs/services/database/include/services/database/Types.hpp b/src/libs/services/database/include/services/database/Types.hpp index 0467fe80..15505e8b 100644 --- a/src/libs/services/database/include/services/database/Types.hpp +++ b/src/libs/services/database/include/services/database/Types.hpp @@ -110,6 +110,8 @@ namespace Database Random, LastWritten, StarredDateDesc, + Name, + DateDescAndRelease, }; enum class TrackArtistLinkType diff --git a/src/libs/services/database/test/Artist.cpp b/src/libs/services/database/test/Artist.cpp index 81dd3771..fd3c11c7 100644 --- a/src/libs/services/database/test/Artist.cpp +++ b/src/libs/services/database/test/Artist.cpp @@ -80,8 +80,6 @@ TEST_F(DatabaseFixture, Artist_singleTrack) ASSERT_EQ(artists.size(), 1); EXPECT_EQ(artists.front()->getId(), artist.getId()); - EXPECT_EQ(artist->getReleaseCount(), 0); - ASSERT_EQ(track->getArtistLinks().size(), 1); auto artistLink {track->getArtistLinks().front()}; EXPECT_EQ(artistLink->getTrack()->getId(), track.getId()); @@ -91,17 +89,6 @@ TEST_F(DatabaseFixture, Artist_singleTrack) EXPECT_TRUE(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty()); EXPECT_EQ(track->getArtists({}).size(), 1); } - - { - auto transaction {session.createUniqueTransaction()}; - - auto tracks {artist->getTracks()}; - ASSERT_EQ(tracks.size(), 1); - EXPECT_EQ(tracks.front()->getId(), track.getId()); - - EXPECT_TRUE(artist->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); - EXPECT_EQ(artist->getTracks(TrackArtistLinkType::Artist).size(), 1); - } } TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles) @@ -143,10 +130,18 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles) EXPECT_EQ(track->getArtistLinks().size(), 3); - EXPECT_EQ(artist->getTracks().size(), 1); - EXPECT_EQ(artist->getTracks({TrackArtistLinkType::ReleaseArtist}).size(), 1); - EXPECT_EQ(artist->getTracks({TrackArtistLinkType::Artist}).size(), 1); - EXPECT_EQ(artist->getTracks({TrackArtistLinkType::Writer}).size(), 1); + auto tracks {Track::find(session, Track::FindParameters {}.setArtist(artist.getId()))}; + EXPECT_EQ(tracks.results.size(), 1); + + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::ReleaseArtist})); + EXPECT_EQ(tracks.results.size(), 1); + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist})); + EXPECT_EQ(tracks.results.size(), 1); + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Writer})); + EXPECT_EQ(tracks.results.size(), 1); + + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Composer})); + EXPECT_EQ(tracks.results.size(), 0); } } @@ -187,13 +182,25 @@ TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists) { auto transaction {session.createUniqueTransaction()}; - EXPECT_EQ(artist1->getTracks().front(), track.get()); - EXPECT_EQ(artist2->getTracks().front(), track.get()); + auto tracks {Track::find(session, Track::FindParameters {}.setArtist(artist1->getId()))}; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track->getId()); - EXPECT_TRUE(artist1->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); - EXPECT_EQ(artist1->getTracks(TrackArtistLinkType::Artist).size(), 1); - EXPECT_TRUE(artist2->getTracks(TrackArtistLinkType::ReleaseArtist).empty()); - EXPECT_EQ(artist2->getTracks(TrackArtistLinkType::Artist).size(), 1); + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId())); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track->getId()); + + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist1->getId(), {TrackArtistLinkType::ReleaseArtist})); + EXPECT_EQ(tracks.results.size(), 0); + + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist1->getId(), {TrackArtistLinkType::Artist})); + EXPECT_EQ(tracks.results.size(), 1); + + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId(), {TrackArtistLinkType::ReleaseArtist})); + EXPECT_EQ(tracks.results.size(), 0); + + tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId(), {TrackArtistLinkType::Artist})); + EXPECT_EQ(tracks.results.size(), 1); } } @@ -324,9 +331,8 @@ TEST_F(DatabaseFixture, Artist_nonReleaseTracks) { auto transaction {session.createSharedTransaction()}; - EXPECT_FALSE(artist->hasNonReleaseTracks(std::nullopt)); - const auto tracks {artist->getNonReleaseTracks(std::nullopt, Range {})}; + auto tracks {Track::find(session, Track::FindParameters {}.setNonRelease(true).setArtist(artist->getId()))}; EXPECT_EQ(tracks.results.size(), 0); } @@ -343,11 +349,9 @@ TEST_F(DatabaseFixture, Artist_nonReleaseTracks) { auto transaction {session.createSharedTransaction()}; - const auto tracks {artist->getNonReleaseTracks(std::nullopt, Range {})}; - EXPECT_TRUE(artist->hasNonReleaseTracks(std::nullopt)); - EXPECT_FALSE(tracks.moreResults); + const auto tracks {Track::find(session, Track::FindParameters {}.setArtist(artist->getId()).setNonRelease(true))}; ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front()->getId(), track2.getId()); + EXPECT_EQ(tracks.results.front(), track2.getId()); } } diff --git a/src/libs/services/database/test/Cluster.cpp b/src/libs/services/database/test/Cluster.cpp index 76ff7be0..d76cf8c5 100644 --- a/src/libs/services/database/test/Cluster.cpp +++ b/src/libs/services/database/test/Cluster.cpp @@ -574,11 +574,11 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster) ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); - auto releases {artist->getReleases(Range {})}; + auto releases {Release::find(session, Release::FindParameters {}.setArtist(artist.getId()))}; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); - releases = artist->getReleases(Range {}, {cluster.getId()}); + releases = Release::find(session, Release::FindParameters {}.setArtist(artist.getId()).setClusters({cluster.getId()})); ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); } @@ -605,11 +605,11 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters) { auto transaction {session.createSharedTransaction()}; - auto releases {artist->getReleases(Range {})}; + auto releases {Release::find(session, Release::FindParameters {}.setArtist(artist.getId()))}; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); - releases = artist->getReleases(Range {}, {cluster1.getId(), cluster2.getId()}); + releases = Release::find(session, Release::FindParameters {}.setArtist(artist.getId()).setClusters({cluster1.getId(), cluster2.getId()})); ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); } diff --git a/src/libs/services/database/test/DatabaseTest.cpp b/src/libs/services/database/test/DatabaseTest.cpp index adf53728..528abd48 100644 --- a/src/libs/services/database/test/DatabaseTest.cpp +++ b/src/libs/services/database/test/DatabaseTest.cpp @@ -49,8 +49,7 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease) { auto transaction {session.createSharedTransaction()}; - EXPECT_EQ(artist->getReleaseCount(), 1); - auto releases {artist->getReleases(Range {})}; + auto releases {Release::find(session, Release::FindParameters {}.setArtist(artist.getId()))}; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); @@ -74,12 +73,10 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtist) { auto transaction {session.createUniqueTransaction()}; - auto releases {artist->getReleases(Range {})}; + auto releases {Release::find(session, Release::FindParameters {}.setArtist(artist.getId()))}; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); - EXPECT_EQ(artist->getReleaseCount(), 1); - auto artists {release->getArtists()}; ASSERT_EQ(artists.size(), 1); ASSERT_EQ(artists.front()->getId(), artist.getId()); diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 0552311f..f5174330 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -422,7 +422,7 @@ releaseToResponseNode(const Release::pointer& release, Session& dbSession, const static Response::Node -artistToResponseNode(const User::pointer& user, const Artist::pointer& artist, bool id3) +artistToResponseNode(const Artist::pointer& artist, Session& session, const User::pointer& user, bool id3) { Response::Node artistNode; @@ -430,7 +430,10 @@ artistToResponseNode(const User::pointer& user, const Artist::pointer& artist, b artistNode.setAttribute("name", artist->getName()); if (id3) - artistNode.setAttribute("albumCount", artist->getReleaseCount()); + { + const auto releases {Release::find(session, Release::FindParameters {}.setArtist(artist->getId()))}; + artistNode.setAttribute("albumCount", releases.results.size()); + } if (Service::get()->isStarred(user->getId(), artist->getId())) artistNode.setAttribute("starred", reportedStarredDate); @@ -884,9 +887,9 @@ handleGetArtistRequest(RequestContext& context) throw UserNotAuthorizedError {}; Response response {Response::createOkResponse(context.serverProtocolVersion)}; - Response::Node artistNode {artistToResponseNode(user, artist, true /* id3 */)}; + Response::Node artistNode {artistToResponseNode(artist, context.dbSession, user, true /* id3 */)}; - auto releases {artist->getReleases(Range {})}; + const auto releases {Release::find(context.dbSession, Release::FindParameters {}.setArtist(artist->getId()))}; for (const ReleaseId releaseId : releases.results) { const Release::pointer release {Release::find(context.dbSession, releaseId)}; @@ -936,7 +939,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3) { const Artist::pointer similarArtist {Artist::find(context.dbSession, similarArtistId)}; if (similarArtist) - artistInfoNode.addArrayChild("similarArtist", artistToResponseNode(user, similarArtist, id3)); + artistInfoNode.addArrayChild("similarArtist", artistToResponseNode(similarArtist, context.dbSession, user, id3)); } } @@ -988,7 +991,7 @@ handleGetMusicDirectoryRequest(RequestContext& context) for (const ArtistId artistId : artistIds.results) { const Artist::pointer artist {Artist::find(context.dbSession, artistId)}; - directoryNode.addArrayChild("child", artistToResponseNode(user, artist, false /* no id3 */)); + directoryNode.addArrayChild("child", artistToResponseNode(artist, context.dbSession, user, false /* no id3 */)); } } else if (artistId) @@ -1001,7 +1004,7 @@ handleGetMusicDirectoryRequest(RequestContext& context) directoryNode.setAttribute("name", makeNameFilesystemCompatible(artist->getName())); - auto releases {artist->getReleases(Range {})}; + const auto releases {Release::find(context.dbSession, Release::FindParameters {}.setArtist(*artistId))}; for (const ReleaseId releaseId : releases.results) { const Release::pointer release {Release::find(context.dbSession, releaseId)}; @@ -1095,7 +1098,7 @@ handleGetArtistsRequestCommon(RequestContext& context, bool id3) indexNode.setAttribute("name", std::string {sortChar}); for (const Artist::pointer& artist :artists) - indexNode.addArrayChild("artist", artistToResponseNode(user, artist, id3)); + indexNode.addArrayChild("artist", artistToResponseNode(artist, context.dbSession, user, id3)); } return response; @@ -1160,26 +1163,34 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3) throw UserNotAuthorizedError {}; // "Returns a random collection of songs from the given artist and similar artists" - auto tracks {artist->getRandomTracks(count / 2)}; + const auto trackResults {Track::find(context.dbSession, Track::FindParameters {} + .setArtist(artist->getId()) + .setRange({0, count / 2}) + .setSortMethod(TrackSortMethod::Random))}; + + std::vector tracks {trackResults.results}; + for (const ArtistId similarArtistId : similarArtistIds) { - const Artist::pointer similarArtist {Artist::find(context.dbSession, similarArtistId)}; - if (!similarArtist) - continue; - - auto similarArtistTracks {similarArtist->getRandomTracks((count / 2) / 5)}; + const auto similarArtistTracks {Track::find(context.dbSession, Track::FindParameters {} + .setArtist(similarArtistId) + .setRange({0, (count / 2) / 5}) + .setSortMethod(TrackSortMethod::Random))}; tracks.insert(std::end(tracks), - std::make_move_iterator(std::begin(similarArtistTracks)), - std::make_move_iterator(std::end(similarArtistTracks))); + std::begin(similarArtistTracks.results), + std::end(similarArtistTracks.results)); } Random::shuffleContainer(tracks); Response response {Response::createOkResponse(context.serverProtocolVersion)}; Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")}; - for (const Track::pointer& track : tracks) + for (const TrackId trackId : tracks) + { + const Track::pointer track {Track::find(context.dbSession, trackId)}; similarSongsNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user)); + } return response; } @@ -1216,7 +1227,7 @@ handleGetStarredRequestCommon(RequestContext& context, bool id3) for (const ArtistId artistId : scrobbling.getStarredArtists(context.userId, {} /* clusters */, std::nullopt /* linkType */, ArtistSortMethod::BySortName, Range {}).results) { if (auto artist {Artist::find(context.dbSession, artistId)}) - starredNode.addArrayChild("artist", artistToResponseNode(user, artist, id3)); + starredNode.addArrayChild("artist", artistToResponseNode(artist, context.dbSession, user, id3)); } for (const ReleaseId releaseId : scrobbling.getStarredReleases(context.userId, {} /* clusters */, Range {}).results) @@ -1433,7 +1444,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3) for (const ArtistId artistId : artistIds.results) { const auto artist {Artist::find(context.dbSession, artistId)}; - searchResult2Node.addArrayChild("artist", artistToResponseNode(user, artist, id3)); + searchResult2Node.addArrayChild("artist", artistToResponseNode(artist, context.dbSession, user, id3)); } } diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index 5fad227b..df9d5927 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -113,17 +113,12 @@ Artist::refreshView() std::size_t sectionCount{}; - LMS_LOG(UI, DEBUG) << "Refresh releases..."; if (refreshReleases()) sectionCount++; - LMS_LOG(UI, DEBUG) << "Refresh appears on releases..."; if (refreshAppearsOnReleases()) sectionCount++; - LMS_LOG(UI, DEBUG) << "Refresh non album tracks..."; - if (refreshNonReleaseTracks(artist)) + if (refreshNonReleaseTracks()) sectionCount++; - - LMS_LOG(UI, DEBUG) << "Refresh links..."; refreshLinks(artist); refreshSimilarArtists(similarArtistIds); @@ -235,11 +230,8 @@ Artist::refreshAppearsOnReleases() } bool -Artist::refreshNonReleaseTracks(const ObjectPtr& artist) +Artist::refreshNonReleaseTracks() { - if (!artist->hasNonReleaseTracks()) - return false; - setCondition("if-has-non-release-tracks", true); _trackContainer = bindNew("tracks"); _trackContainer->onRequestElements.connect(this, [this] @@ -247,7 +239,9 @@ Artist::refreshNonReleaseTracks(const ObjectPtr& artist) addSomeNonReleaseTracks(); }); - return addSomeNonReleaseTracks(); + const bool added {addSomeNonReleaseTracks()}; + setCondition("if-has-non-release-tracks", added); + return added; } void @@ -317,14 +311,19 @@ Artist::addSomeNonReleaseTracks() bool areTracksAdded{}; auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - const Database::Artist::pointer artist {Database::Artist::find(LmsApp->getDbSession(), _artistId)}; - if (!artist) - return areTracksAdded; + const Range range {static_cast(_trackContainer->getCount()), _tracksBatchSize}; - const auto tracks {artist->getNonReleaseTracks(std::nullopt, Range {static_cast(_trackContainer->getCount()), _tracksBatchSize})}; + Track::FindParameters params; + params.setClusters(_filters->getClusterIds()); + params.setArtist(_artistId); + params.setRange(range); + params.setSortMethod(TrackSortMethod::Name); + params.setNonRelease(true); + + const auto tracks {Track::find(LmsApp->getDbSession(), params)}; bool moreResults {tracks.moreResults}; - for (const Track::pointer& track : tracks.results) + for (const TrackId trackId : tracks.results) { if (_trackContainer->getCount() == _tracksMaxCount) { @@ -332,6 +331,7 @@ Artist::addSomeNonReleaseTracks() break; } + const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)}; _trackContainer->add(TrackListHelpers::createEntry(track, tracksAction)); areTracksAdded = true; diff --git a/src/lms/ui/explore/ArtistView.hpp b/src/lms/ui/explore/ArtistView.hpp index ccec3d5d..b1dd8cf5 100644 --- a/src/lms/ui/explore/ArtistView.hpp +++ b/src/lms/ui/explore/ArtistView.hpp @@ -50,7 +50,7 @@ namespace UserInterface void refreshView(); bool refreshReleases(); bool refreshAppearsOnReleases(); - bool refreshNonReleaseTracks(const Database::ObjectPtr& artist); + bool refreshNonReleaseTracks(); void refreshSimilarArtists(const std::vector& similarArtistsId); void refreshLinks(const Database::ObjectPtr& artist); diff --git a/src/lms/ui/explore/Explore.cpp b/src/lms/ui/explore/Explore.cpp index 3998a0a2..13e2432b 100644 --- a/src/lms/ui/explore/Explore.cpp +++ b/src/lms/ui/explore/Explore.cpp @@ -130,7 +130,7 @@ Explore::search(const Wt::WString& searchText) static std::vector -getArtistsTracks(Database::Session& session, const std::vector& artistsId, const std::vector&) +getArtistsTracks(Database::Session& session, const std::vector& artistsId, const std::vector& clusters) { std::vector res; @@ -138,15 +138,10 @@ getArtistsTracks(Database::Session& session, const std::vectorgetDbSession(), Database::Track::FindParameters {}.setArtist(artistId).setClusters(clusters).setSortMethod(Database::TrackSortMethod::DateDescAndRelease))}; - // TODO handle clusters here - const std::vector tracks {artist->getTracks()}; - - res.reserve(res.size() + tracks.size()); - std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const Database::Track::pointer& track) { return track->getId(); }); + res.reserve(res.size() + tracks.results.size()); + res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results)); } return res; diff --git a/src/lms/ui/resource/DownloadResource.cpp b/src/lms/ui/resource/DownloadResource.cpp index 92643f08..23e2abe9 100644 --- a/src/lms/ui/resource/DownloadResource.cpp +++ b/src/lms/ui/resource/DownloadResource.cpp @@ -145,6 +145,9 @@ static std::unique_ptr createZipper(const std::vector& tracks) { + if (tracks.empty()) + return {}; + std::map files; for (const Database::Track::pointer& track : tracks) @@ -185,14 +188,14 @@ DownloadArtistResource::createZipper() { auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - const Database::Artist::pointer artist {Database::Artist::find(LmsApp->getDbSession(), _artistId)}; - if (!artist) - { - LOG(DEBUG) << "Cannot find artist"; - return {}; - } + const auto trackResults {Database::Track::find(LmsApp->getDbSession(), Database::Track::FindParameters {}.setArtist(_artistId).setSortMethod(Database::TrackSortMethod::DateDescAndRelease))}; + std::vector tracks; + tracks.reserve(trackResults.results.size()); - return UserInterface::createZipper(artist->getTracks()); + for (const Database::TrackId trackId : trackResults.results) + tracks.push_back(Database::Track::find(LmsApp->getDbSession(), trackId)); + + return UserInterface::createZipper(tracks); } DownloadReleaseResource::DownloadReleaseResource(Database::ReleaseId releaseId)