From 4ab91aea4451400bcb822698b19af95537c8f82f Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 22 Oct 2019 12:47:25 +0200 Subject: [PATCH] Improved the tag based similarity results --- src/database/Artist.cpp | 31 +- src/database/Artist.hpp | 9 +- src/database/Cluster.cpp | 14 +- src/database/Cluster.hpp | 2 +- src/database/Release.cpp | 36 +- src/database/Release.hpp | 7 +- src/database/Track.cpp | 40 +- src/database/Track.hpp | 10 +- src/database/TrackList.cpp | 33 + src/database/TrackList.hpp | 4 + src/similarity/SimilaritySearcher.cpp | 31 + src/similarity/SimilaritySearcher.hpp | 1 + .../cluster/SimilarityClusterSearcher.cpp | 142 +- .../cluster/SimilarityClusterSearcher.hpp | 1 + src/ui/PlayQueueView.cpp | 15 +- test/database/DatabaseTest.cpp | 1375 +++++++++-------- 16 files changed, 924 insertions(+), 827 deletions(-) diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp index 25aa7d41..1951b69a 100644 --- a/src/database/Artist.cpp +++ b/src/database/Artist.cpp @@ -140,8 +140,10 @@ getQuery(Session& session, } std::vector -Artist::getByFilter(Session& session, const std::set& clusters) +Artist::getByClusters(Session& session, const std::set& clusters) { + assert(!clusters.empty()); + session.checkSharedLocked(); bool more; return getByFilter(session, clusters, {}, {}, {}, more); @@ -296,6 +298,33 @@ Artist::getRandomTracks(std::optional count) const return std::vector>(tracks.begin(), tracks.end()); } +std::vector> +Artist::getSimilarArtists(std::optional offset, std::optional count) const +{ + assert(self()); + assert(IdIsValid(self()->id())); + assert(session()); + + Wt::Dbo::Query query {session()->query( + "SELECT a FROM artist a" + " INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" + " INNER JOIN track t ON t.id = t_a_l.track_id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " WHERE " + " t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id 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 = ?)" + " AND a.id <> ?" + ) + .bind(self()->id()) + .bind(self()->id()) + .groupBy("a.id") + .orderBy("COUNT(*) DESC") + .limit(count ? static_cast(*count) : -1) + .offset(offset ? static_cast(*offset) : -1)}; + + Wt::Dbo::collection res = query; + return std::vector(res.begin(), res.end()); +} + std::vector>> Artist::getClusterGroups(std::vector clusterTypes, std::size_t size) const { diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp index 13b58943..b46cafb1 100644 --- a/src/database/Artist.hpp +++ b/src/database/Artist.hpp @@ -52,11 +52,11 @@ class Artist : public Wt::Dbo::Dbo static pointer getByMBID(Session& session, const std::string& MBID); static pointer getById(Session& session, IdType id); static std::vector getByName(Session& session, const std::string& name); - static std::vector getByFilter(Session& session, + static std::vector getByClusters(Session& session, const std::set& clusters); // at least one track that belongs to these clusters static std::vector getByFilter(Session& session, - const std::set& clusters, // at least one track that belongs to these clusters - const std::vector& keywords, // name must match all of these keywords + const std::set& clusters, // if non empty, at least one artist that belongs to these clusters + const std::vector& keywords, // if non empty, name must match all of these keywords std::optional offset, std::optional size, bool& moreExpected); @@ -69,11 +69,12 @@ class Artist : public Wt::Dbo::Dbo const std::string& getName(void) const { return _name; } const std::string& getMBID(void) const { return _MBID; } - std::vector> getReleases(const std::set& clusterIds = std::set()) const; + std::vector> getReleases(const std::set& 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; std::vector> getTracksWithRelease(std::optional linkType = {}) const; std::vector> getRandomTracks(std::optional count) const; + std::vector getSimilarArtists(std::optional offset = {}, std::optional count = {}) const; // Get the cluster of the tracks made by this artist // Each clusters are grouped by cluster type, sorted by the number of occurence diff --git a/src/database/Cluster.cpp b/src/database/Cluster.cpp index f07bd778..ddfafb43 100644 --- a/src/database/Cluster.cpp +++ b/src/database/Cluster.cpp @@ -63,8 +63,7 @@ std::vector Cluster::getAllOrphans(Session& session) { session.checkSharedLocked(); - - Wt::Dbo::collection res {session.getDboSession().query("SELECT DISTINCT c FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_cluster t_c ON t.id = t_c.track_id)")}; + Wt::Dbo::collection res {session.getDboSession().query("SELECT DISTINCT c FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)")}; return std::vector(res.begin(), res.end()); } @@ -84,15 +83,16 @@ Cluster::addTrack(Wt::Dbo::ptr track) } std::vector> -Cluster::getTracks(int offset, int limit) const +Cluster::getTracks(std::optional offset, std::optional limit) const { assert(session()); assert(IdIsValid(self()->id())); - Wt::Dbo::collection res = session()->query("SELECT t FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id") - .where("c.id = ?").bind(self()->id()) - .offset(offset) - .limit(limit); + Wt::Dbo::collection res + {session()->query("SELECT t FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id") + .where("c.id = ?").bind(self()->id()) + .offset(offset ? static_cast(*offset) : -1) + .limit(limit ? static_cast(*limit) : -1)}; return std::vector>(res.begin(), res.end()); } diff --git a/src/database/Cluster.hpp b/src/database/Cluster.hpp index 109e49d0..3d0642a6 100644 --- a/src/database/Cluster.hpp +++ b/src/database/Cluster.hpp @@ -55,7 +55,7 @@ class Cluster : public Wt::Dbo::Dbo const std::string& getName() const { return _name; } Wt::Dbo::ptr getType() const { return _clusterType; } std::size_t getTracksCount() const { return _tracks.size(); } - std::vector> getTracks(int offset, int limit) const; + std::vector> getTracks(std::optional offset = {}, std::optional limit = {}) const; std::set getTrackIds() const; std::size_t getReleasesCount() const; diff --git a/src/database/Release.cpp b/src/database/Release.cpp index 40e0ce09..30587939 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -206,10 +206,14 @@ getQuery(Session& session, } std::vector -Release::getByFilter(Session& session, const std::set& clusterIds) +Release::getByClusters(Session& session, const std::set& clusters) { + assert(!clusters.empty()); + + session.checkSharedLocked(); + bool moreResults; - return getByFilter(session, clusterIds, {}, {}, {}, moreResults); + return getByFilter(session, clusters, {}, {}, {}, moreResults); } std::vector @@ -220,6 +224,8 @@ Release::getByFilter(Session& session, std::optional size, bool& moreResults) { + session.checkSharedLocked(); + Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords) .limit(size ? static_cast(*size) + 1 : -1) .offset(offset ? static_cast(*offset) : -1); @@ -332,6 +338,32 @@ Release::getArtists(TrackArtistLink::Type linkType) const return std::vector>(res.begin(), res.end()); } +std::vector +Release::getSimilarReleases(std::optional offset, std::optional count) const +{ + assert(self()); + assert(IdIsValid(self()->id())); + assert(session()); + + Wt::Dbo::Query query {session()->query( + "SELECT r FROM release r" + " INNER JOIN track t ON t.release_id = r.id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " WHERE " + " t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id WHERE r.id = ?)" + " AND r.id <> ?" + ) + .bind(self()->id()) + .bind(self()->id()) + .groupBy("r.id") + .orderBy("COUNT(*) DESC") + .limit(count ? static_cast(*count) : -1) + .offset(offset ? static_cast(*offset) : -1)}; + + Wt::Dbo::collection res = query; + return std::vector(res.begin(), res.end()); +} + bool Release::hasVariousArtists() const { diff --git a/src/database/Release.hpp b/src/database/Release.hpp index f0510257..494bee49 100644 --- a/src/database/Release.hpp +++ b/src/database/Release.hpp @@ -57,10 +57,10 @@ class Release : public Wt::Dbo::Dbo static std::vector getLastAdded(Session& session, const Wt::WDateTime& after, std::optional offset = {}, std::optional size = {}); static std::vector getByYear(Session& session, int yearFrom, int yearTo, std::optional offset = {}, std::optional size = {}); - static std::vector getByFilter(Session& session, const std::set& clusters); + static std::vector getByClusters(Session& session, const std::set& clusters); static std::vector getByFilter(Session& session, - const std::set& clusters, // at least one track that belongs to these clusters - const std::vector& keywords, // name must match all of these keywords + const std::set& clusters, // if non empty, at least one release that belongs to these clusters + const std::vector& keywords, // if non empty, name must match all of these keywords std::optional offset, std::optional size, bool& moreExpected); @@ -96,6 +96,7 @@ class Release : public Wt::Dbo::Dbo std::vector > getArtists(TrackArtistLink::Type type = TrackArtistLink::Type::Artist) const; std::vector > getReleaseArtists() const { return getArtists(TrackArtistLink::Type::ReleaseArtist); } bool hasVariousArtists() const; + std::vector getSimilarReleases(std::optional offset = {}, std::optional count = {}) const; void setMBID(std::string mbid) { _MBID = mbid; } diff --git a/src/database/Track.cpp b/src/database/Track.cpp index e88d7ffb..f188c7ed 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -248,9 +248,47 @@ Track::getByFilter(Session& session, } std::vector -Track::getByFilter(Session& session, +Track::getSimilarTracks(Session& session, + const std::set& tracks, + std::optional offset, + std::optional size) +{ + assert(!tracks.empty()); + session.checkSharedLocked(); + + std::ostringstream oss; + for (std::size_t i {}; i < tracks.size(); ++i) + { + if (!oss.str().empty()) + oss << ", "; + oss << "?"; + } + + Wt::Dbo::Query query {session.getDboSession().query( + "SELECT t FROM track t" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " AND t_c.cluster_id IN (SELECT c.id FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id WHERE t_c.track_id IN (" + oss.str() + "))" + " AND t.id NOT IN (" + oss.str() + ")") + .groupBy("t.id") + .orderBy("COUNT(*) DESC") + .limit(size ? static_cast(*size) : -1) + .offset(offset ? static_cast(*offset) : -1)}; + + for (IdType trackId : tracks) + query.bind(trackId ); + + for (IdType trackId : tracks) + query.bind(trackId ); + + Wt::Dbo::collection res = query; + return std::vector(res.begin(), res.end()); +} + +std::vector +Track::getByClusters(Session& session, const std::set& clusters) { + assert(!clusters.empty()); session.checkSharedLocked(); bool moreResults; diff --git a/src/database/Track.hpp b/src/database/Track.hpp index b926a23b..2ed74a11 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -55,11 +55,15 @@ class Track : public Wt::Dbo::Dbo static pointer getByPath(Session& session, const std::filesystem::path& p); static pointer getById(Session& session, IdType id); static pointer getByMBID(Session& session, const std::string& MBID); - static std::vector getByFilter(Session& session, + static std::vector getSimilarTracks(Session& session, + const std::set& trackIds, + std::optional offset = {}, + std::optional size = {}); + static std::vector getByClusters(Session& session, const std::set& clusters); // tracks that belong to these clusters static std::vector getByFilter(Session& session, - const std::set& clusters, // tracks that belong to these clusters - const std::vector& keywords, // name must match all of these keywords + const std::set& clusters, // if non empty, tracks that belong to these clusters + const std::vector& keywords, // if non empty, name must match all of these keywords std::optional offset, std::optional size, bool& moreExpected); diff --git a/src/database/TrackList.cpp b/src/database/TrackList.cpp index 79d1a6c3..d63312ca 100644 --- a/src/database/TrackList.cpp +++ b/src/database/TrackList.cpp @@ -65,6 +65,15 @@ TrackList::get(Session& session, const std::string& name, Type type, Wt::Dbo::pt .where("user_id = ?").bind(user.id()); } +std::vector +TrackList::getAll(Session& session) +{ + session.checkSharedLocked(); + Wt::Dbo::collection res = session.getDboSession().find(); + + return std::vector(res.begin(), res.end()); +} + std::vector TrackList::getAll(Session& session, Wt::Dbo::ptr user) { @@ -176,6 +185,30 @@ TrackList::hasTrack(IdType trackId) const return res.size() > 0; } +std::vector +TrackList::getSimilarTracks(std::optional offset, std::optional size) const +{ + assert(session()); + assert(IdIsValid(self()->id())); + + Wt::Dbo::Query query {session()->query( + "SELECT t FROM track t" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " WHERE " + " (t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id WHERE p.id = ?)" + " AND t.id NOT IN (SELECT tracklist_t.id FROM track tracklist_t INNER JOIN tracklist_entry t_e ON t_e.track_id = tracklist_t.id WHERE t_e.tracklist_id = ?))" + ) + .bind(self()->id()) + .bind(self()->id()) + .groupBy("t.id") + .orderBy("COUNT(*) DESC") + .limit(size ? static_cast(*size) : -1) + .offset(offset ? static_cast(*offset) : -1)}; + + Wt::Dbo::collection tracks = query; + return std::vector(tracks.begin(), tracks.end()); +} + std::vector TrackList::getTrackIds() const { diff --git a/src/database/TrackList.hpp b/src/database/TrackList.hpp index 2e41ade9..6b38435d 100644 --- a/src/database/TrackList.hpp +++ b/src/database/TrackList.hpp @@ -58,6 +58,7 @@ class TrackList : public Wt::Dbo::Dbo // Search utility static pointer get(Session& session, const std::string& name, Type type, Wt::Dbo::ptr user); static pointer getById(Session& session, IdType tracklistId); + static std::vector getAll(Session& session); static std::vector getAll(Session& session, Wt::Dbo::ptr user); static std::vector getAll(Session& session, Wt::Dbo::ptr user, Type type); @@ -90,6 +91,9 @@ class TrackList : public Wt::Dbo::Dbo bool hasTrack(IdType trackId) const; + // Ordered from most clusters in common + std::vector> getSimilarTracks(std::optional offset = {}, std::optional size = {}) const; + template void persist(Action& a) { diff --git a/src/similarity/SimilaritySearcher.cpp b/src/similarity/SimilaritySearcher.cpp index e34abb22..34e43835 100644 --- a/src/similarity/SimilaritySearcher.cpp +++ b/src/similarity/SimilaritySearcher.cpp @@ -23,6 +23,7 @@ #include "cluster/SimilarityClusterSearcher.hpp" #include "database/SimilaritySettings.hpp" +#include "database/TrackList.hpp" namespace Similarity { @@ -37,6 +38,36 @@ Database::SimilaritySettings::EngineType getEngineType(Database::Session& dbSess return Database::SimilaritySettings::get(dbSession)->getEngineType(); } +std::vector +Searcher::getSimilarTracksFromTrackList(Database::Session& session, Database::IdType trackListId, std::size_t maxCount) +{ + auto engineType {getEngineType(session)}; + auto somSearcher {_somAddon.getSearcher()}; + + std::set trackIds; + { + auto transaction {session.createSharedTransaction()}; + Database::TrackList::pointer trackList {Database::TrackList::getById(session, trackListId)}; + if (trackList) + { + const std::vector orderedTrackIds {trackList->getTrackIds()}; + trackIds = std::set {std::cbegin(orderedTrackIds), std::cend(orderedTrackIds)}; + } + } + + if (trackIds.empty()) + return {}; + + if (engineType == Database::SimilaritySettings::EngineType::Features + && somSearcher + && std::any_of(std::cbegin(trackIds), std::cend(trackIds), [&](Database::IdType trackId) { return somSearcher->isTrackClassified(trackId); } )) + { + return somSearcher->getSimilarTracks(trackIds, maxCount); + } + else + return ClusterSearcher::getSimilarTracksFromTrackList(session, trackListId, maxCount); +} + std::vector Searcher::getSimilarTracks(Database::Session& dbSession, const std::set& trackIds, std::size_t maxCount) { diff --git a/src/similarity/SimilaritySearcher.hpp b/src/similarity/SimilaritySearcher.hpp index 56d942ca..3a53cb9b 100644 --- a/src/similarity/SimilaritySearcher.hpp +++ b/src/similarity/SimilaritySearcher.hpp @@ -39,6 +39,7 @@ class Searcher Searcher(FeaturesScannerAddon& somAddon); // Closest results first + std::vector getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount); std::vector getSimilarTracks(Database::Session& session, const std::set& tracksId, std::size_t maxCount); std::vector getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount); std::vector getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount); diff --git a/src/similarity/cluster/SimilarityClusterSearcher.cpp b/src/similarity/cluster/SimilarityClusterSearcher.cpp index bc48f500..c4a7e1aa 100644 --- a/src/similarity/cluster/SimilarityClusterSearcher.cpp +++ b/src/similarity/cluster/SimilarityClusterSearcher.cpp @@ -27,71 +27,42 @@ #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" +#include "database/TrackList.hpp" #include "utils/Utils.hpp" namespace Similarity { namespace ClusterSearcher { -static -std::vector -getSimilarTracksLocked(Database::Session& dbSession, const std::set& trackIds, std::size_t maxCount) -{ - std::vector clusterIds; - for (auto trackId : trackIds) - { - auto track {Database::Track::getById(dbSession, trackId)}; - if (!track) - continue; - - auto clusters = track->getClusters(); - if (clusters.empty()) - continue; - - for (const auto& cluster : clusters) - clusterIds.push_back(cluster.id()); - } - - std::vector sortedClusterIds; - uniqueAndSortedByOccurence(clusterIds.begin(), clusterIds.end(), std::back_inserter(sortedClusterIds)); - - std::vector res; - for (auto clusterId : clusterIds) - { - auto cluster {Database::Cluster::getById(dbSession, clusterId)}; - if (!cluster) - continue; - - std::set clusterTrackIds = cluster->getTrackIds(); - - std::set candidateTrackIds; - std::set_difference(clusterTrackIds.begin(), clusterTrackIds.end(), - trackIds.begin(), trackIds.end(), - std::inserter(candidateTrackIds, candidateTrackIds.end())); - - if (candidateTrackIds.empty()) - continue; - - for (auto trackId : candidateTrackIds) - { - if (res.size() >= maxCount) - break; - - res.push_back(trackId); - } - - if (res.size() >= maxCount) - break; - } - - return res; -} - std::vector getSimilarTracks(Database::Session& dbSession, const std::set& trackIds, std::size_t maxCount) { auto transaction {dbSession.createSharedTransaction()}; - return getSimilarTracksLocked(dbSession, trackIds, maxCount); + auto tracks {Database::Track::getSimilarTracks(dbSession, trackIds, 0, maxCount)}; + std::vector res; + res.reserve(tracks.size()); + + std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), [](const auto& track) { return track.id(); }); + return res; +} + +std::vector +getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount) +{ + std::vector res; + + auto transaction {session.createSharedTransaction()}; + + const Database::TrackList::pointer trackList {Database::TrackList::getById(session, tracklistId)}; + if (!trackList) + return res; + + const std::vector tracks {trackList->getSimilarTracks(0, maxCount)}; + res.reserve(tracks.size()); + std::transform(std::cbegin(tracks), std::cend(tracks), std::back_inserter(res), + [](const Database::Track::pointer& track) { return track.id(); }); + + return res; } std::vector @@ -105,31 +76,9 @@ getSimilarReleases(Database::Session& dbSession, Database::IdType releaseId, std if (!release) return res; - auto releaseTracks = release->getTracks(); - std::set releaseTrackIds; - - for (const auto& releaseTrack : releaseTracks) - releaseTrackIds.insert(releaseTrack.id()); - - auto trackIds {getSimilarTracksLocked(dbSession, releaseTrackIds, maxCount * 5)}; - for (auto trackId : trackIds) - { - auto track {Database::Track::getById(dbSession, trackId)}; - if (!track) - continue; - - auto trackRelease = track->getRelease(); - if (!trackRelease || trackRelease.id() == releaseId) - continue; - - if (std::find(res.begin(), res.end(), trackRelease.id()) != res.end()) - continue; - - res.push_back(trackRelease.id()); - - if (res.size() == maxCount) - break; - } + const auto releases {release->getSimilarReleases(0, maxCount)}; + res.reserve(releases.size()); + std::transform(std::cbegin(releases), std::cend(releases), std::back_inserter(res), [](const auto& release) { return release.id(); }); return res; } @@ -145,36 +94,9 @@ getSimilarArtists(Database::Session& dbSession, Database::IdType artistId, std:: if (!artist) return res; - auto artistTracks {artist->getTracks()}; - std::set artistTrackIds; - - for (const auto& artistTrack : artistTracks) - artistTrackIds.insert(artistTrack.id()); - - auto trackIds {getSimilarTracksLocked(dbSession, artistTrackIds, maxCount * 5)}; - for (auto trackId : trackIds) - { - auto track {Database::Track::getById(dbSession, trackId)}; - if (!track) - continue; - - for (const auto& trackArtist : track->getArtists()) - { - if (!trackArtist || trackArtist.id() == artistId) - continue; - - if (std::find(res.begin(), res.end(), trackArtist.id()) != res.end()) - continue; - - res.push_back(trackArtist.id()); - - if (res.size() == maxCount) - break; - } - - if (res.size() == maxCount) - break; - } + const auto artists {artist->getSimilarArtists(0, maxCount)}; + res.reserve(artists.size()); + std::transform(std::cbegin(artists), std::cend(artists), std::back_inserter(res), [](const auto& artist) { return artist.id(); }); return res; } diff --git a/src/similarity/cluster/SimilarityClusterSearcher.hpp b/src/similarity/cluster/SimilarityClusterSearcher.hpp index 176fc362..90ef0db8 100644 --- a/src/similarity/cluster/SimilarityClusterSearcher.hpp +++ b/src/similarity/cluster/SimilarityClusterSearcher.hpp @@ -31,6 +31,7 @@ namespace Similarity { namespace ClusterSearcher { + std::vector getSimilarTracksFromTrackList(Database::Session& session, Database::IdType tracklistId, std::size_t maxCount); std::vector getSimilarTracks(Database::Session& session, const std::set& tracksId, std::size_t maxCount); std::vector getSimilarReleases(Database::Session& session, Database::IdType releaseId, std::size_t maxCount); std::vector getSimilarArtists(Database::Session& session, Database::IdType artistId, std::size_t maxCount); diff --git a/src/ui/PlayQueueView.cpp b/src/ui/PlayQueueView.cpp index 3dcd69e6..ae2baf48 100644 --- a/src/ui/PlayQueueView.cpp +++ b/src/ui/PlayQueueView.cpp @@ -22,6 +22,7 @@ #include #include +#include "database/Cluster.hpp" #include "database/Track.hpp" #include "database/TrackList.hpp" #include "database/User.hpp" @@ -411,19 +412,7 @@ PlayQueue::addSome() void PlayQueue::enqueueRadioTrack() { - std::vector trackIds; - - { - auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - Database::TrackList::pointer tracklist {getTrackList()}; - - trackIds = getTrackList()->getTrackIds(); - } - - if (trackIds.empty()) - return; - - const std::vector trackToAddIds {getService()->getSimilarTracks(LmsApp->getDbSession(), std::set(std::cbegin(trackIds), std::cend(trackIds)), 1)}; + const std::vector trackToAddIds {getService()->getSimilarTracksFromTrackList(LmsApp->getDbSession(), _tracklistId, 1)}; enqueueTracks(trackToAddIds); } diff --git a/test/database/DatabaseTest.cpp b/test/database/DatabaseTest.cpp index 7d767c1b..e53dc774 100644 --- a/test/database/DatabaseTest.cpp +++ b/test/database/DatabaseTest.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "database/Artist.hpp" #include "database/Cluster.hpp" @@ -31,23 +32,105 @@ using namespace Database; +#define CHECK(PRED) \ + do \ + { \ + try \ + { \ + if (!(PRED)) \ + { \ + std::string error {"Predicate FAILED '" + std::string {#PRED} + "' at " + __FUNCTION__ + "@l." + std::to_string(__LINE__)}; \ + std::cerr << error << std::endl; \ + throw std::runtime_error {error}; \ + } \ + } \ + catch (std::exception& e) \ + { \ + std::cerr << "Exception caught: " << e.what() << std::endl; \ + throw; \ + } \ + } while (0) + + class ScopedFileDeleter final { public: ScopedFileDeleter(const std::filesystem::path& path) : _path {path} {} ~ScopedFileDeleter() { std::filesystem::remove(_path); } + + ScopedFileDeleter(const ScopedFileDeleter&) = delete; + ScopedFileDeleter(ScopedFileDeleter&&) = delete; + ScopedFileDeleter operator=(const ScopedFileDeleter&) = delete; + ScopedFileDeleter operator=(ScopedFileDeleter&&) = delete; + private: - std::filesystem::path _path; + const std::filesystem::path _path; }; -#define CHECK(PRED) \ -{ \ - if (!(PRED)) \ - { \ - std::string msg {"Predicate '" + std::string {#PRED} + "' at " + __FUNCTION__ + "@l." + std::to_string(__LINE__)}; \ - throw std::runtime_error(msg.c_str()); \ - } \ -} +template +class ScopedEntity +{ + public: + + template + ScopedEntity(Session& session, Args&& ...args) + : _session {session} + { + auto transaction {_session.createUniqueTransaction()}; + + auto entity {T::create(_session, std::forward(args)...)}; + CHECK(entity); + _id = entity.id(); + } + + ~ScopedEntity() + { + auto transaction {_session.createUniqueTransaction()}; + + auto entity {T::getById(_session, _id)}; + entity.remove(); + } + + ScopedEntity(const ScopedEntity&) = delete; + ScopedEntity(ScopedEntity&&) = delete; + ScopedEntity& operator=(const ScopedEntity&) = delete; + ScopedEntity& operator=(ScopedEntity&&) = delete; + + typename T::pointer lockAndGet() + { + auto transaction {_session.createSharedTransaction()}; + return get(); + } + + typename T::pointer get() + { + _session.checkSharedLocked(); + + auto entity {T::getById(_session, _id)}; + CHECK(entity); + return entity; + } + + typename T::pointer operator->() + { + return get(); + } + + IdType getId() const { return _id; } + + private: + Session& _session; + IdType _id {}; +}; + +using ScopedArtist = ScopedEntity; +using ScopedCluster = ScopedEntity; +using ScopedClusterType = ScopedEntity; +using ScopedRelease = ScopedEntity; +using ScopedTrack = ScopedEntity; +using ScopedTrackList = ScopedEntity; +using ScopedUser = ScopedEntity; + static void @@ -66,24 +149,12 @@ static void testSingleTrack(Session& session) { - IdType trackId {}; - { - auto transaction {session.createUniqueTransaction()}; - - auto track {Track::create(session, "MyTrackFile")}; - CHECK(track); - CHECK(IdIsValid(track.id())); - trackId = track.id(); - } + ScopedTrack track {session, "MyTrackFile"}; { auto transaction {session.createUniqueTransaction()}; - auto track {Track::getById(session, trackId)}; - CHECK(track); CHECK(Track::getAll(session).size() == 1); - - track.remove(); } } @@ -91,31 +162,18 @@ static void testSingleArtist(Session& session) { - IdType artistId {}; - { - auto transaction {session.createUniqueTransaction()}; - - auto artist {Artist::create(session, "MyArtist")}; - CHECK(artist); - artistId = artist.id(); - } + ScopedArtist artist {session, "MyArtist"}; { auto transaction {session.createSharedTransaction()}; - CHECK(Artist::getAll(session).size() == 1); - - auto artists {Artist::getAllOrphans(session)}; + auto artists {Artist::getAll(session)}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); - } + CHECK(artists.front().id() == artist.getId()); - { - auto transaction {session.createUniqueTransaction()}; - - auto artist {Artist::getById(session, artistId)}; - CHECK(artist); - artist.remove(); + artists = Artist::getAllOrphans(session); + CHECK(artists.size() == 1); + CHECK(artists.front().id() == artist.getId()); } } @@ -123,33 +181,18 @@ static void testSingleRelease(Session& session) { - IdType releaseId {}; - { - auto transaction {session.createUniqueTransaction()}; - - auto release {Release::create(session, "MyRelease")}; - CHECK(release); - releaseId = release.id(); - } + ScopedRelease release {session, "MyRelease"}; { auto transaction {session.createSharedTransaction()}; auto releases {Release::getAllOrphans(session)}; CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); + CHECK(releases.front().id() == release.getId()); releases = Release::getAll(session); CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto release {Release::getById(session, releaseId)}; - CHECK(release); - release.remove(); + CHECK(releases.front().id() == release.getId()); } } @@ -157,57 +200,39 @@ static void testSingleCluster(Session& session) { - IdType clusterTypeId {}; - IdType clusterId {}; - { - auto transaction {session.createUniqueTransaction()}; - - auto clusterType {ClusterType::create(session, "MyType")}; - CHECK(clusterType); - auto cluster {Cluster::create(session, clusterType, "MyCluster")}; - CHECK(cluster); - clusterTypeId = clusterType.id(); - clusterId = cluster.id(); - } + ScopedClusterType clusterType {session, "MyType"}; { - auto transaction {session.createSharedTransaction()}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; - auto clusters {Cluster::getAll(session)}; - CHECK(clusters.size() == 1); - CHECK(clusters.front().id() == clusterId); - CHECK(clusters.front()->getType().id() == clusterTypeId); + { + auto transaction {session.createUniqueTransaction()}; - clusters = Cluster::getAllOrphans(session); - CHECK(clusters.size() == 1); - CHECK(clusters.front().id() == clusterId); - auto clusterTypes {ClusterType::getAll(session)}; - CHECK(clusterTypes.size() == 1); - CHECK(clusterTypes.front().id() == clusterTypeId); + auto clusters {Cluster::getAll(session)}; + CHECK(clusters.size() == 1); + CHECK(clusters.front().id() == cluster.getId()); + CHECK(clusters.front()->getType().id() == clusterType.getId()); - clusterTypes = ClusterType::getAllOrphans(session); - CHECK(clusterTypes.empty()); + clusters = Cluster::getAllOrphans(session); + CHECK(clusters.size() == 1); + CHECK(clusters.front().id() == cluster.getId()); + + auto clusterTypes {ClusterType::getAll(session)}; + CHECK(clusterTypes.size() == 1); + CHECK(clusterTypes.front().id() == clusterType.getId()); + + clusterTypes = ClusterType::getAllOrphans(session); + CHECK(clusterTypes.empty()); + } } { auto transaction {session.createUniqueTransaction()}; - auto cluster {Cluster::getById(session, clusterId)}; - CHECK(cluster); - cluster.remove(); - auto clusterTypes {ClusterType::getAllOrphans(session)}; CHECK(clusterTypes.size() == 1); - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto clusterType {ClusterType::getById(session, clusterTypeId)}; - CHECK(clusterType); - - clusterType.remove(); + CHECK(clusterTypes.front().id() == clusterType.getId()); } } @@ -215,20 +240,13 @@ static void testSingleTrackSingleArtist(Session& session) { - IdType trackId {}; - IdType artistId {}; + ScopedTrack track {session, "MyTrack"}; + ScopedArtist artist {session, "MyArtist"}; + { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "dummy")}; - CHECK(track); - auto artist {Artist::create(session, "dummy")}; - CHECK(artist); - - auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; - - trackId = track.id(); - artistId = artist.id(); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist); } { @@ -239,20 +257,16 @@ testSingleTrackSingleArtist(Session& session) { auto transaction {session.createSharedTransaction()}; - auto track {Track::getById(session, trackId)}; - CHECK(track); - auto artists {track->getArtists()}; CHECK(artists.size() == 1); - auto artist {artists.front()}; - CHECK(artist.id() == artistId); + CHECK(artists.front().id() == artist.getId()); CHECK(artist->getReleaseCount() == 0); CHECK(track->getArtistLinks().size() == 1); auto artistLink {track->getArtistLinks().front()}; - CHECK(artistLink->getTrack().id() == trackId); - CHECK(artistLink->getArtist().id() == artistId); + CHECK(artistLink->getTrack().id() == track.getId()); + CHECK(artistLink->getArtist().id() == artist.getId()); CHECK(track->getArtists(TrackArtistLink::Type::Artist).size() == 1); CHECK(track->getArtists(TrackArtistLink::Type::ReleaseArtist).empty()); @@ -261,18 +275,12 @@ testSingleTrackSingleArtist(Session& session) { auto transaction {session.createUniqueTransaction()}; - auto artist {Artist::getById(session, artistId)}; - CHECK(artist); auto tracks {artist->getTracks()}; CHECK(tracks.size() == 1); - auto track {tracks.front()}; - CHECK(track.id() == trackId); + CHECK(tracks.front().id() == track.getId()); CHECK(artist->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist->getTracks(TrackArtistLink::Type::Artist).size() == 1); - - track.remove(); - artist.remove(); } } @@ -280,20 +288,14 @@ static void testSingleTrackSingleArtistMultiRoles(Session& session) { - IdType trackId {}; - IdType artistId {}; + ScopedTrack track {session, "MyTrack"}; + ScopedArtist artist {session, "MyArtist"}; { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "MyTrack")}; - auto artist {Artist::create(session, "MyArtist")}; - - TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist); - TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::ReleaseArtist); - TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Writer); - - trackId = track.id(); - artistId = artist.id(); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::ReleaseArtist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Writer); } { @@ -304,60 +306,37 @@ testSingleTrackSingleArtistMultiRoles(Session& session) { auto transaction {session.createSharedTransaction()}; - auto track {Track::getById(session, trackId)}; - CHECK(track); - auto artists {track->getArtists(TrackArtistLink::Type::Artist)}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); + CHECK(artists.front().id() == artist.getId()); artists = track->getArtists(TrackArtistLink::Type::ReleaseArtist); CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); + CHECK(artists.front().id() == artist.getId()); CHECK(track->getArtistLinks().size() == 3); - auto artist {Artist::getById(session, artistId)}; - CHECK(artist); CHECK(artist->getTracks().size() == 1); CHECK(artist->getTracks(TrackArtistLink::Type::ReleaseArtist).size() == 1); CHECK(artist->getTracks(TrackArtistLink::Type::Artist).size() == 1); CHECK(artist->getTracks(TrackArtistLink::Type::Writer).size() == 1); } - - { - auto transaction {session.createUniqueTransaction()}; - - auto artist {Artist::getById(session, artistId)}; - auto track {Track::getById(session, trackId)}; - - track.remove(); - artist.remove(); - } } static void testSingleTrackMultiArtists(Session& session) { - IdType trackId {}; - IdType artist1Id {}; - IdType artist2Id {}; + ScopedTrack track {session, "track"}; + ScopedArtist artist1 {session, "artist1"}; + ScopedArtist artist2 {session, "artist2"}; + CHECK(artist1.getId() != artist2.getId()); + { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "dummy")}; - auto artist1 {Artist::create(session, "artist1")}; - auto artist2 {Artist::create(session, "artist2")}; - - TrackArtistLink::create(session, track, artist1, TrackArtistLink::Type::Artist); - TrackArtistLink::create(session, track, artist2, TrackArtistLink::Type::Artist); - - trackId = track.id(); - artist1Id = artist1.id(); - artist2Id = artist2.id(); - - CHECK(artist1Id != artist2Id); + TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLink::Type::Artist); + TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLink::Type::Artist); } { @@ -368,13 +347,10 @@ testSingleTrackMultiArtists(Session& session) { auto transaction {session.createSharedTransaction()}; - auto track {Track::getById(session, trackId)}; - CHECK(track); - auto artists {track->getArtists()}; CHECK(artists.size() == 2); - CHECK((artists[0].id() == artist1Id && artists[1].id() == artist2Id) - || (artists[0].id() == artist2Id && artists[1].id() == artist1Id)); + CHECK((artists[0].id() == artist1.getId() && artists[1].id() == artist2.getId()) + || (artists[0].id() == artist2.getId() && artists[1].id() == artist1.getId())); CHECK(track->getArtists(TrackArtistLink::Type::Artist).size() == 2); CHECK(track->getArtists(TrackArtistLink::Type::ReleaseArtist).empty()); @@ -384,24 +360,13 @@ testSingleTrackMultiArtists(Session& session) { auto transaction {session.createUniqueTransaction()}; - auto artist1 {Artist::getById(session, artist1Id)}; - CHECK(artist1); - auto tracks {artist1->getTracks()}; - CHECK(tracks.size() == 1); - auto track {tracks.front()}; - CHECK(track.id() == trackId); - auto artist2 {Artist::getById(session, artist2Id)}; - CHECK(artist2); - CHECK(artist2->getTracks().front() == track); + CHECK(artist1->getTracks().front() == track.get()); + CHECK(artist2->getTracks().front() == track.get()); CHECK(artist1->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist1->getTracks(TrackArtistLink::Type::Artist).size() == 1); CHECK(artist2->getTracks(TrackArtistLink::Type::ReleaseArtist).empty()); CHECK(artist2->getTracks(TrackArtistLink::Type::Artist).size() == 1); - - track.remove(); - artist1.remove(); - artist2.remove(); } } @@ -409,52 +374,41 @@ static void testSingleTrackSingleRelease(Session& session) { - IdType trackId {}; - IdType releaseId {}; - { - auto transaction {session.createUniqueTransaction()}; - - auto track {Track::create(session, "dummy")}; - CHECK(track); - auto release {Release::create(session, "dummy")}; - CHECK(release); - - track.modify()->setRelease(release); - - trackId = track.id(); - releaseId = release.id(); - } + ScopedRelease release {session, "MyRelease"}; { - auto transaction {session.createSharedTransaction()}; - CHECK(Release::getAllOrphans(session).empty()); + ScopedTrack track {session, "MyTrack"}; + { + auto transaction {session.createUniqueTransaction()}; - auto release {Release::getById(session, releaseId)}; - CHECK(release); - CHECK(release->getTracks().size() == 1); - CHECK(release->getTracks().front().id() == trackId); - CHECK(release->getTracksCount() == 1); + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction {session.createSharedTransaction()}; + CHECK(Release::getAllOrphans(session).empty()); + + CHECK(release->getTracks().size() == 1); + CHECK(release->getTracksCount() == 1); + CHECK(release->getTracks().front().id() == track.getId()); + } + + { + auto transaction {session.createUniqueTransaction()}; + + CHECK(track->getRelease()); + CHECK(track->getRelease().id() == release.getId()); + } } { auto transaction {session.createUniqueTransaction()}; - auto track {Track::getById(session, trackId)}; - CHECK(track); - CHECK(track->getRelease()); - CHECK(track->getRelease().id() == releaseId); - track.remove(); - } - - { - auto transaction {session.createUniqueTransaction()}; - - CHECK(Release::getAllOrphans(session).size() == 1); - auto release {Release::getById(session, releaseId)}; - CHECK(release); CHECK(release->getTracks().empty()); - release.remove(); + auto releases {Release::getAllOrphans(session)}; + CHECK(releases.size() == 1); + CHECK(releases.front().id() == release.getId()); } } @@ -462,50 +416,160 @@ static void testSingleTrackSingleCluster(Session& session) { - IdType trackId {}; - IdType clusterId {}; - IdType clusterTypeId {}; + ScopedTrack track {session, "MyTrack"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + + { + auto transaction {session.createSharedTransaction()}; + CHECK(Cluster::getAllOrphans(session).empty()); + auto clusterTypes {ClusterType::getAllOrphans(session)}; + CHECK(clusterTypes.size() ==1); + CHECK(clusterTypes.front().id() == clusterType.getId()); + } + + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + { + auto transaction {session.createSharedTransaction()}; + auto clusters {Cluster::getAllOrphans(session)}; + CHECK(clusters.size() == 2); + } + { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "dummy")}; - auto clusterType {ClusterType::create(session, "MyType")}; - auto cluster {Cluster::create(session, clusterType, "MyCluster")}; + cluster1.get().modify()->addTrack(track.get()); + } - cluster.modify()->addTrack(track); + { + auto transaction {session.createSharedTransaction()}; + auto clusters {Cluster::getAllOrphans(session)}; + CHECK(clusters.size() == 1); + CHECK(clusters.front().id() == cluster2.getId()); - trackId = track.id(); - clusterTypeId = clusterType.id(); - clusterId = cluster.id(); + CHECK(ClusterType::getAllOrphans(session).empty()); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto tracks {Track::getByClusters(session, {cluster1.getId()})}; + CHECK(tracks.size() == 1); + CHECK(tracks.front().id() == track.getId()); + + tracks = Track::getByClusters(session, {cluster2.getId()}); + CHECK(tracks.empty()); + } +} + +static +void +testMultipleTracksSingleCluster(Session& session) +{ + std::list tracks; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyClusterType"}; + + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction {session.createUniqueTransaction()}; + cluster.get().modify()->addTrack(tracks.back().get()); + } } { auto transaction {session.createSharedTransaction()}; CHECK(Cluster::getAllOrphans(session).empty()); + + CHECK(cluster->getTracksCount() == tracks.size()); + + for (auto trackCluster : cluster->getTracks()) + { + auto it {std::find_if(std::cbegin(tracks), std::cend(tracks), [&](const ScopedTrack& track) { return trackCluster.id() == track.getId(); })}; + CHECK(it != std::cend(tracks)); + } + } +} + + +static +void +testMultipleTracksSingleClusterSimilarity(Session& session) +{ + std::list tracks; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyClusterType"}; + + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction {session.createUniqueTransaction()}; + cluster.get().modify()->addTrack(tracks.back().get()); + } } { auto transaction {session.createSharedTransaction()}; - auto tracks {Track::getByFilter(session, {clusterId})}; - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == trackId); + const auto similarTracks {Track::getSimilarTracks(session, {tracks.front().getId()})}; + CHECK(similarTracks.size() == tracks.size() - 1); + for (auto similarTrack : similarTracks) + CHECK(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const ScopedTrack& track) { return similarTrack.id() == track.getId(); }) != std::cend(tracks)); + } +} - tracks = Track::getByFilter(session, {}); - CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == trackId); +static +void +testMultipleTracksMultipleClustersSimilarity(Session& session) +{ + std::list tracks; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + for (std::size_t i {}; i < 5; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction {session.createUniqueTransaction()}; + cluster1.get().modify()->addTrack(tracks.back().get()); + } + } + + for (std::size_t i {5}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction {session.createUniqueTransaction()}; + cluster1.get().modify()->addTrack(tracks.back().get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } } { - auto transaction {session.createUniqueTransaction()}; + auto transaction {session.createSharedTransaction()}; - auto track {Track::getById(session, trackId)}; - auto cluster {Cluster::getById(session, clusterId)}; - auto clusterType {ClusterType::getById(session, clusterTypeId)}; + { + auto similarTracks {Track::getSimilarTracks(session, {tracks.back().getId()}, 0, 4)}; + CHECK(similarTracks.size() == 4); + for (auto similarTrack : similarTracks) + CHECK(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const ScopedTrack& track) { return similarTrack.id() == track.getId(); }) != std::cend(tracks)); + } - track.remove(); - cluster.remove(); - clusterType.remove(); + { + auto similarTracks {Track::getSimilarTracks(session, {tracks.front().getId()})}; + CHECK(similarTracks.size() == tracks.size() - 1); + for (auto similarTrack : similarTracks) + CHECK(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const ScopedTrack& track) { return similarTrack.id() == track.getId(); }) != std::cend(tracks)); + } } } @@ -513,99 +577,62 @@ static void testSingleTrackSingleReleaseSingleCluster(Session& session) { - IdType trackId {}; - IdType releaseId {}; - IdType clusterId {}; - IdType clusterTypeId {}; + ScopedTrack track {session, "MyTrackFile"}; + ScopedRelease release {session, "MyRelease"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster {session, clusterType .lockAndGet(), "MyCluster"}; + { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "MyTrackFile")}; - auto release {Release::create(session, "MyRelease")}; - auto clusterType {ClusterType::create(session, "MyType")}; - auto cluster {Cluster::create(session, clusterType, "MyCluster")}; - - track.modify()->setRelease(release); - cluster.modify()->addTrack(track); - - trackId = track.id(); - releaseId = release.id(); - clusterTypeId = clusterType.id(); - clusterId = cluster.id(); + track.get().modify()->setRelease(release.get()); + cluster.get().modify()->addTrack(track.get()); } { auto transaction {session.createSharedTransaction()}; + CHECK(Cluster::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty()); } { - auto transaction {session.createUniqueTransaction()}; + auto transaction {session.createSharedTransaction()}; - auto releases {Release::getByFilter(session, {clusterId})}; + auto releases {Release::getByClusters(session, {cluster.getId()})}; CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); - - releases = Release::getByFilter(session, {clusterId}); - CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); + CHECK(releases.front().id() == release.getId()); } { auto transaction {session.createSharedTransaction()}; - auto cluster {Cluster::getById(session, clusterId)}; CHECK(cluster->getReleasesCount() == 1); CHECK(cluster->getTracksCount() == 1); } - - { - auto transaction {session.createUniqueTransaction()}; - - auto track {Track::getById(session, trackId)}; - auto release {Release::getById(session, releaseId)}; - auto cluster {Cluster::getById(session, clusterId)}; - auto clusterType {ClusterType::getById(session, clusterTypeId)}; - - track.remove(); - release.remove(); - cluster.remove(); - clusterType.remove(); - } } static void testSingleTrackSingleArtistMultiClusters(Session& session) { - IdType trackId {}; - IdType artistId {}; - IdType cluster1Id {}; - IdType cluster2Id {}; - IdType clusterTypeId {}; + ScopedTrack track {session, "MyTrackFile"}; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "Cluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "Cluster2"}; + ScopedCluster cluster3 {session, clusterType.lockAndGet(), "Cluster3"}; { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "MyTrackFile")}; - auto artist {Artist::create(session, "MyArtist")}; - auto clusterType {ClusterType::create(session, "MyType")}; - auto cluster1 {Cluster::create(session, clusterType, "MyCluster1")}; - auto cluster2 {Cluster::create(session, clusterType, "MyCluster2")}; - - auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; - cluster1.modify()->addTrack(track); - - trackId = track.id(); - artistId = artist.id(); - clusterTypeId = clusterType.id(); - cluster1Id = cluster1.id(); - cluster2Id = cluster2.id(); + auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist)}; + cluster1.get().modify()->addTrack(track.get()); } { auto transaction {session.createSharedTransaction()}; - CHECK(Cluster::getAllOrphans(session).empty()); + CHECK(ClusterType::getAllOrphans(session).empty()); + CHECK(Cluster::getAllOrphans(session).size() == 2); CHECK(Release::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty()); } @@ -613,52 +640,32 @@ testSingleTrackSingleArtistMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByFilter(session, {cluster1Id})}; + auto artists {Artist::getByClusters(session, {cluster1.getId()})}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); + CHECK(artists.front().id() == artist.getId()); - artists = Artist::getByFilter(session, {}); - CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); + CHECK(Artist::getByClusters(session, {cluster2.getId()}).empty()); + CHECK(Artist::getByClusters(session, {cluster3.getId()}).empty()); - artists = Artist::getByFilter(session, {cluster2Id}); - CHECK(artists.empty()); - - auto cluster2 {Cluster::getById(session, cluster2Id)}; - auto track {Track::getById(session, trackId)}; - cluster2.modify()->addTrack(track); + cluster2.get().modify()->addTrack(track.get()); } { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByFilter(session, {cluster1Id})}; + auto artists {Artist::getByClusters(session, {cluster1.getId()})}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); + CHECK(artists.front().id() == artist.getId()); - artists = Artist::getByFilter(session, {cluster2Id}); + artists = Artist::getByClusters(session, {cluster2.getId()}); CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); + CHECK(artists.front().id() == artist.getId()); - artists = Artist::getByFilter(session, {cluster1Id, cluster2Id}); + artists = Artist::getByClusters(session, {cluster1.getId(), cluster2.getId()}); CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); - } + CHECK(artists.front().id() == artist.getId()); - { - auto transaction {session.createUniqueTransaction()}; - - auto track {Track::getById(session, trackId)}; - auto artist {Artist::getById(session, artistId)}; - auto cluster1 {Cluster::getById(session, cluster1Id)}; - auto cluster2 {Cluster::getById(session, cluster2Id)}; - auto clusterType {ClusterType::getById(session, clusterTypeId)}; - - track.remove(); - artist.remove(); - cluster1.remove(); - cluster2.remove(); - clusterType.remove(); + CHECK(Artist::getByClusters(session, {cluster3.getId()}).empty()); } } @@ -666,26 +673,17 @@ static void testSingleTrackSingleArtistMultiRolesMultiClusters(Session& session) { - IdType trackId {}; - IdType artistId {}; - IdType clusterId {}; - IdType clusterTypeId {}; + ScopedTrack track {session, "MyTrackFile"}; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "MyTrackFile")}; - auto artist {Artist::create(session, "MyArtist")}; - auto clusterType {ClusterType::create(session, "MyType")}; - auto cluster {Cluster::create(session, clusterType, "MyCluster")}; - - TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist); - TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::ReleaseArtist); - cluster.modify()->addTrack(track); - - trackId = track.id(); - artistId = artist.id(); - clusterTypeId = clusterType.id(); - clusterId = cluster.id(); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::ReleaseArtist); + cluster.get().modify()->addTrack(track.get()); } { @@ -698,23 +696,9 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByFilter(session, {clusterId})}; + auto artists {Artist::getByClusters(session, {cluster.getId()})}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto track {Track::getById(session, trackId)}; - auto artist {Artist::getById(session, artistId)}; - auto cluster {Cluster::getById(session, clusterId)}; - auto clusterType {ClusterType::getById(session, clusterTypeId)}; - - track.remove(); - artist.remove(); - cluster.remove(); - clusterType.remove(); + CHECK(artists.front().id() == artist.getId()); } } @@ -722,32 +706,26 @@ static void testMultiTracksSingleArtistMultiClusters(Session& session) { - const std::size_t nbTracks {10}; - const std::size_t nbClusters {5}; - IdType artistId {}; - IdType clusterTypeId {}; + constexpr std::size_t nbTracks {10}; + constexpr std::size_t nbClusters {5}; + + std::list tracks; + std::list clusters; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyType"}; + + for (std::size_t i {}; i < nbClusters; ++i) + clusters.emplace_back(session, clusterType.lockAndGet(), "MyCluster" + std::to_string(i)); + + for (std::size_t i {}; i < nbTracks ; ++i) { + tracks.emplace_back(session, "MyTrackFile" + std::to_string(i)); + auto transaction {session.createUniqueTransaction()}; + TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLink::Type::Artist); - auto artist {Artist::create(session, "MyArtist")}; - - auto clusterType {ClusterType::create(session, "MyType")}; - std::vector clusters; - - for (std::size_t i {}; i < nbClusters; ++i) - clusters.push_back(Cluster::create(session, clusterType, "MyCluster" + std::to_string(i))); - - for (std::size_t i {}; i < nbTracks; ++i) - { - auto track {Track::create(session, "MyTrackFile")}; - TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist); - - for (const auto& cluster : clusters) - cluster.modify()->addTrack(track); - } - - artistId = artist.id(); - clusterTypeId = clusterType.id(); + for (auto& cluster : clusters) + cluster.get().modify()->addTrack(tracks.back().get()); } { @@ -759,33 +737,12 @@ testMultiTracksSingleArtistMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - std::vector clusters {Cluster::getAll(session)}; - CHECK(clusters.size() == nbClusters); - std::set clusterIds; - std::transform(std::cbegin(clusters), std::cend(clusters), std::inserter(clusterIds, std::begin(clusterIds)), [](const Cluster::pointer& cluster) { return cluster.id(); }); + std::transform(std::cbegin(clusters), std::cend(clusters), std::inserter(clusterIds, std::begin(clusterIds)), [](const ScopedCluster& cluster) { return cluster.getId(); }); - auto artists {Artist::getByFilter(session, clusterIds)}; + auto artists {Artist::getByClusters(session, clusterIds)}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); - } - - { - auto transaction {session.createUniqueTransaction()}; - - std::vector clusters {Cluster::getAll(session)}; - for (auto& cluster : clusters) - cluster.remove(); - - std::vector tracks {Track::getAll(session)}; - for (auto& track : tracks) - track.remove(); - - auto artist {Artist::getById(session, artistId)}; - auto clusterType {ClusterType::getById(session, clusterTypeId)}; - - artist.remove(); - clusterType.remove(); + CHECK(artists.front().id() == artist.getId()); } } @@ -793,24 +750,19 @@ static void testMultiTracksSingleArtistSingleRelease(Session& session) { - const std::size_t nbTracks {10}; - IdType artistId {}; - IdType releaseId {}; + constexpr std::size_t nbTracks {10}; + std::list tracks; + ScopedArtist artist {session, "MyArtst"}; + ScopedRelease release {session, "MyRelease"}; + + for (std::size_t i {}; i < nbTracks; ++i) { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + auto transaction {session.createUniqueTransaction()}; - auto artist {Artist::create(session, "MyArtist")}; - auto release {Release::create(session, "MyRelease")}; - - for (std::size_t i {}; i < nbTracks; ++i) - { - auto track {Track::create(session, "MyTrackFile")}; - TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist); - track.modify()->setRelease(release); - } - - artistId = artist.id(); - releaseId = release.id(); + TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLink::Type::Artist); + tracks.back().get().modify()->setRelease(release.get()); } { @@ -822,75 +774,42 @@ testMultiTracksSingleArtistSingleRelease(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artist {Artist::getById(session, artistId)}; - CHECK(artist); CHECK(artist->getReleaseCount() == 1); CHECK(artist->getReleases().size() == 1); - CHECK(artist->getReleases().front().id() == releaseId); + CHECK(artist->getReleases().front().id() == release.getId()); - auto release {Release::getById(session, releaseId)}; - CHECK(release); CHECK(release->getTracks().size() == nbTracks); } - { - auto transaction {session.createUniqueTransaction()}; - - std::vector tracks {Track::getAll(session)}; - for (auto& track : tracks) - track.remove(); - - auto artist {Artist::getById(session, artistId)}; - auto release {Release::getById(session, releaseId)}; - artist.remove(); - release.remove(); - } } static void testSingleTrackSingleReleaseSingleArtist(Session& session) { - IdType trackId {}; - IdType releaseId {}; - IdType artistId {}; + ScopedTrack track {session, "MyTrack"}; + ScopedRelease release {session, "MyRelease"}; + ScopedArtist artist {session, "MyArtist"}; + { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "dummy")}; - auto release {Release::create(session, "dummy")}; - auto artist {Artist::create(session, "dummy")}; - - auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; - track.modify()->setRelease(release); - - trackId = track.id(); - releaseId = release.id(); - artistId = artist.id(); + auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist)}; + track.get().modify()->setRelease(release.get()); } { auto transaction {session.createUniqueTransaction()}; - auto artist {Artist::getById(session, artistId)}; - CHECK(artist); auto releases {artist->getReleases()}; CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); + CHECK(releases.front().id() == release.getId()); CHECK(artist->getReleaseCount() == 1); - auto release {Release::getById(session, releaseId)}; - CHECK(release); auto artists {release->getArtists()}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); - - auto track {Track::getById(session, trackId)}; - - track.remove(); - artist.remove(); - release.remove(); + CHECK(artists.front().id() == artist.getId()); } } @@ -898,37 +817,25 @@ static void testSingleTrackSingleReleaseSingleArtistSingleCluster(Session& session) { - IdType trackId {}; - IdType releaseId {}; - IdType artistId {}; - IdType clusterId {}; - IdType clusterTypeId {}; + ScopedTrack track {session, "MyTrack"}; + ScopedRelease release {session, "MyRelease"}; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "MyTrackFile")}; - auto release {Release::create(session, "MyRelease")}; - auto artist {Artist::create(session, "MyArtist")}; - - auto clusterType {ClusterType::create(session, "MyType")}; - auto cluster {Cluster::create(session, clusterType, "MyCluster")}; - - auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; - track.modify()->setRelease(release); - cluster.modify()->addTrack(track); - - trackId = track.id(); - releaseId = release.id(); - artistId = artist.id(); - clusterId = cluster.id(); - clusterTypeId = clusterType.id(); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist); + track.get().modify()->setRelease(release.get()); + cluster.get().modify()->addTrack(track.get()); } { auto transaction {session.createSharedTransaction()}; CHECK(Cluster::getAllOrphans(session).empty()); + CHECK(ClusterType::getAllOrphans(session).empty()); CHECK(Artist::getAllOrphans(session).empty()); CHECK(Release::getAllOrphans(session).empty()); } @@ -936,34 +843,17 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByFilter(session, {clusterId})}; + auto artists {Artist::getByClusters(session, {cluster.getId()})}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); + CHECK(artists.front().id() == artist.getId()); - auto artist {Artist::getById(session, artistId)}; auto releases {artist->getReleases()}; CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); + CHECK(releases.front().id() == release.getId()); - releases = artist->getReleases({clusterId}); + releases = artist->getReleases({cluster.getId()}); CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto artist {Artist::getById(session, artistId)}; - auto release {Release::getById(session, releaseId)}; - auto track {Track::getById(session, trackId)}; - auto cluster {Cluster::getById(session, clusterId)}; - auto clusterType {ClusterType::getById(session, clusterTypeId)}; - - track.remove(); - artist.remove(); - release.remove(); - cluster.remove(); - clusterType.remove(); + CHECK(releases.front().id() == release.getId()); } } @@ -971,66 +861,32 @@ static void testSingleTrackSingleReleaseSingleArtistMultiClusters(Session& session) { - IdType trackId {}; - IdType releaseId {}; - IdType artistId {}; - IdType cluster1Id {}; - IdType cluster2Id {}; - IdType clusterTypeId {}; + ScopedTrack track {session, "MyTrack"}; + ScopedRelease release {session, "MyRelease"}; + ScopedArtist artist {session, "MyArtist"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; { auto transaction {session.createUniqueTransaction()}; - auto track {Track::create(session, "MyTrackFile")}; - auto release {Release::create(session, "MyRelease")}; - auto artist {Artist::create(session, "MyArtist")}; - - auto clusterType {ClusterType::create(session, "MyType")}; - auto cluster1 {Cluster::create(session, clusterType, "MyCluster1")}; - auto cluster2 {Cluster::create(session, clusterType, "MyCluster2")}; - - auto trackArtistLink {TrackArtistLink::create(session, track, artist, TrackArtistLink::Type::Artist)}; - track.modify()->setRelease(release); - cluster1.modify()->addTrack(track); - cluster2.modify()->addTrack(track); - - trackId = track.id(); - releaseId = release.id(); - artistId = artist.id(); - cluster1Id = cluster1.id(); - cluster2Id = cluster2.id(); - clusterTypeId = clusterType.id(); + auto trackArtistLink {TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLink::Type::Artist)}; + track.get().modify()->setRelease(release.get()); + cluster1.get().modify()->addTrack(track.get()); + cluster2.get().modify()->addTrack(track.get()); } { auto transaction {session.createSharedTransaction()}; - auto artist {Artist::getById(session, artistId)}; auto releases {artist->getReleases()}; CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); + CHECK(releases.front().id() == release.getId()); - releases = artist->getReleases({cluster1Id, cluster2Id}); + releases = artist->getReleases({cluster1.getId(), cluster2.getId()}); CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto artist {Artist::getById(session, artistId)}; - auto release {Release::getById(session, releaseId)}; - auto track {Track::getById(session, trackId)}; - auto cluster1 {Cluster::getById(session, cluster1Id)}; - auto cluster2 {Cluster::getById(session, cluster1Id)}; - auto clusterType {ClusterType::getById(session, clusterTypeId)}; - - track.remove(); - artist.remove(); - release.remove(); - cluster1.remove(); - cluster2.remove(); - clusterType.remove(); + CHECK(releases.front().id() == release.getId()); } } @@ -1038,89 +894,40 @@ static void testSingleUser(Session& session) { - IdType userId {}; - { - auto transaction {session.createUniqueTransaction()}; - - auto user {User::create(session, "", {})}; - CHECK(user); - - userId = user.id(); - } + ScopedUser user {session, "MyUser", User::PasswordHash {}}; { auto transaction {session.createSharedTransaction()}; - auto user {User::getById(session, userId)}; - CHECK(user); CHECK(user->getPlayedTrackList(session)->getCount() == 0); CHECK(user->getPlayedTrackList(session)->getTopTracks(1).empty()); CHECK(user->getPlayedTrackList(session)->getTopArtists(1).empty()); CHECK(user->getPlayedTrackList(session)->getTopReleases(1).empty()); CHECK(user->getQueuedTrackList(session)->getCount() == 0); } - - { - auto transaction {session.createUniqueTransaction()}; - - auto user {User::getById(session, userId)}; - CHECK(user); - user.remove(); - } } static void testSingleStarredArtist(Session& session) { - IdType artistId {}; - IdType userId {}; + ScopedArtist artist {session, "MyArtist"}; + ScopedUser user {session, "MyUser", User::PasswordHash {}}; + { - auto transaction {session.createUniqueTransaction()}; + auto transaction {session.createSharedTransaction()}; - auto artist {Artist::create(session, "MyArtist")}; - CHECK(artist); - auto user {User::create(session, "", {})}; - CHECK(user); - - artistId = artist.id(); - userId = user.id(); + user.get().modify()->starArtist(artist.get()); } { auto transaction {session.createSharedTransaction()}; - auto user {User::getById(session, userId)}; - CHECK(user); - auto artist {Artist::getById(session, artistId)}; - CHECK(artist); - - user.modify()->starArtist(artist); - } - - { - auto transaction {session.createSharedTransaction()}; - - auto user {User::getById(session, userId)}; - CHECK(user); auto artists {user->getStarredArtists()}; CHECK(artists.size() == 1); - CHECK(artists.front().id() == artistId); + CHECK(artists.front().id() == artist.getId()); - auto artist {Artist::getById(session, artistId)}; - CHECK(user->hasStarredArtist(artist)); - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto user {User::getById(session, userId)}; - CHECK(user); - auto artist {Artist::getById(session, artistId)}; - CHECK(artist); - - user.remove(); - artist.remove(); + CHECK(user->hasStarredArtist(artist.get())); } } @@ -1128,54 +935,23 @@ static void testSingleStarredRelease(Session& session) { - IdType releaseId {}; - IdType userId {}; - { - auto transaction {session.createUniqueTransaction()}; - - auto release {Release::create(session, "MyRelease")}; - CHECK(release); - auto user {User::create(session, "", {})}; - CHECK(user); - - releaseId = release.id(); - userId = user.id(); - } + ScopedRelease release {session, "MyRelease"}; + ScopedUser user {session, "MyUser", User::PasswordHash {}}; { auto transaction {session.createUniqueTransaction()}; - auto user {User::getById(session, userId)}; - CHECK(user); - auto release {Release::getById(session, releaseId)}; - CHECK(release); - - user.modify()->starRelease(release); + user.get().modify()->starRelease(release.get()); } { auto transaction {session.createSharedTransaction()}; - auto user {User::getById(session, userId)}; - CHECK(user); auto releases {user->getStarredReleases()}; CHECK(releases.size() == 1); - CHECK(releases.front().id() == releaseId); + CHECK(releases.front().id() == release.getId()); - auto release {Release::getById(session, releaseId)}; - CHECK(user->hasStarredRelease(release)); - } - - { - auto transaction {session.createUniqueTransaction()}; - - auto user {User::getById(session, userId)}; - CHECK(user); - auto release {Release::getById(session, releaseId)}; - CHECK(release); - - user.remove(); - release.remove(); + CHECK(user->hasStarredRelease(release.get())); } } @@ -1183,54 +959,277 @@ static void testSingleStarredTrack(Session& session) { - IdType trackId {}; - IdType userId {}; - { - auto transaction {session.createUniqueTransaction()}; - - auto track {Track::create(session, "MyTrackFile")}; - CHECK(track); - auto user {User::create(session, "", {})}; - CHECK(user); - - trackId = track.id(); - userId = user.id(); - } + ScopedTrack track {session, "MyTrack"}; + ScopedUser user {session, "MyUser", User::PasswordHash {}}; { auto transaction {session.createUniqueTransaction()}; - auto user {User::getById(session, userId)}; - CHECK(user); - auto track {Track::getById(session, trackId)}; - CHECK(track); - - user.modify()->starTrack(track); + user.get().modify()->starTrack(track.get()); } { auto transaction {session.createSharedTransaction()}; - auto user {User::getById(session, userId)}; - CHECK(user); auto tracks {user->getStarredTracks()}; CHECK(tracks.size() == 1); - CHECK(tracks.front().id() == trackId); + CHECK(tracks.front().id() == track.getId()); - auto track {Track::getById(session, trackId)}; - CHECK(user->hasStarredTrack(track)); + CHECK(user->hasStarredTrack(track.get())); + } +} + +static +void +testSingleTrackList(Session& session) +{ + ScopedUser user {session, "MyUser", User::PasswordHash {}}; + ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; + + { + auto transaction {session.createSharedTransaction()}; + + auto trackLists {TrackList::getAll(session, user.get(), TrackList::Type::Playlist)}; + CHECK(trackLists.size() == 1); + CHECK(trackLists.front().id() == trackList.getId()); + } + +} + +static +void +testSingleTrackListMultipleTrack(Session& session) +{ + ScopedUser user {session, "MyUser", User::PasswordHash {}}; + ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; + std::list tracks; + + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + TrackListEntry::create(session, tracks.back().get(), trackList.get()); } { + auto transaction {session.createSharedTransaction()}; + + CHECK(trackList->getCount() == tracks.size()); + const auto trackIds {trackList->getTrackIds()}; + for (auto trackId : trackIds) + CHECK(std::any_of(std::cbegin(tracks), std::cend(tracks), [trackId](const ScopedTrack& track) { return track.getId() == trackId; })); + } +} + +static +void +testSingleTrackListMultipleTrackSingleCluster(Session& session) +{ + ScopedUser user {session, "MyUser", User::PasswordHash {}}; + ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"}; + std::list tracks; + + for (std::size_t i {}; i < 20; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + auto transaction {session.createUniqueTransaction()}; - auto user {User::getById(session, userId)}; - CHECK(user); - auto track {Track::getById(session, trackId)}; - CHECK(track); + if (i < 5) + TrackListEntry::create(session, tracks.back().get(), trackList.get()); - user.remove(); - track.remove(); + if (i < 10) + cluster.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + const auto similarTracks {trackList->getSimilarTracks()}; + CHECK(similarTracks.size() == 5); + + for (auto similarTrack : similarTracks) + CHECK(std::any_of(std::next(std::cbegin(tracks), 5), std::cend(tracks), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); + } +} + +static +void +testSingleTrackListMultipleTrackMultiClusters(Session& session) +{ + ScopedUser user {session, "MyUser", User::PasswordHash {}}; + ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + std::list tracks; + + for (std::size_t i {}; i < 20; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + + if (i < 5) + TrackListEntry::create(session, tracks.back().get(), trackList.get()); + + if (i < 10) + { + cluster1.get().modify()->addTrack(tracks.back().get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + else if (i < 15) + { + cluster1.get().modify()->addTrack(tracks.back().get()); + } + } + + { + auto transaction {session.createSharedTransaction()}; + + { + const auto similarTracks {trackList->getSimilarTracks(0, 5)}; + CHECK(similarTracks.size() == 5); + + for (auto similarTrack : similarTracks) + CHECK(std::any_of(std::next(std::cbegin(tracks), 5), std::next(std::cbegin(tracks), 10), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); + } + + { + const auto similarTracks {trackList->getSimilarTracks(5, 10)}; + CHECK(similarTracks.size() == 5); + + for (auto similarTrack : similarTracks) + CHECK(std::any_of(std::next(std::cbegin(tracks), 10), std::next(std::cbegin(tracks), 15), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack.id(); })); + } + + CHECK(trackList->getSimilarTracks(10, 10).empty()); + + } +} + +static +void +testMultipleTracksMultipleArtistsMultiClusters(Session& session) +{ + ScopedArtist artist1 {session, "MyArtist1"}; + ScopedArtist artist2 {session, "MyArtist2"}; + ScopedArtist artist3 {session, "MyArtist3"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + { + auto transaction {session.createSharedTransaction()}; + CHECK(artist1->getSimilarArtists().empty()); + CHECK(artist2->getSimilarArtists().empty()); + CHECK(artist3->getSimilarArtists().empty()); + } + + std::list tracks; + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + + if (i < 5) + TrackArtistLink::create(session, tracks.back().get(), artist1.get(), TrackArtistLink::Type::Artist); + else + { + TrackArtistLink::create(session, tracks.back().get(), artist2.get(), TrackArtistLink::Type::Artist); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + cluster1.get().modify()->addTrack(tracks.back().get()); + } + + tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); + { + auto transaction {session.createUniqueTransaction()}; + TrackArtistLink::create(session, tracks.back().get(), artist3.get(), TrackArtistLink::Type::Artist); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + { + auto artists {artist1->getSimilarArtists()}; + CHECK(artists.size() == 1); + CHECK(artists.front().id() == artist2.getId()); + } + + { + auto artists {artist2->getSimilarArtists()}; + CHECK(artists.size() == 2); + CHECK(artists[0].id() == artist1.getId()); + CHECK(artists[1].id() == artist3.getId()); + } + } +} + +static +void +testMultipleTracksMultipleReleasesMultiClusters(Session& session) +{ + ScopedRelease release1 {session, "MyRelease1"}; + ScopedRelease release2 {session, "MyRelease2"}; + ScopedRelease release3 {session, "MyRelease3"}; + ScopedClusterType clusterType {session, "MyClusterType"}; + ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"}; + ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"}; + + { + auto transaction {session.createSharedTransaction()}; + CHECK(release1->getSimilarReleases().empty()); + CHECK(release2->getSimilarReleases().empty()); + CHECK(release3->getSimilarReleases().empty()); + } + + std::list tracks; + for (std::size_t i {}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction {session.createUniqueTransaction()}; + + if (i < 5) + tracks.back().get().modify()->setRelease(release1.get()); + else + { + tracks.back().get().modify()->setRelease(release2.get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + cluster1.get().modify()->addTrack(tracks.back().get()); + } + + tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); + { + auto transaction {session.createUniqueTransaction()}; + tracks.back().get().modify()->setRelease(release3.get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction {session.createSharedTransaction()}; + + { + auto releases {release1->getSimilarReleases()}; + CHECK(releases.size() == 1); + CHECK(releases.front().id() == release2.getId()); + } + + { + auto releases {release2->getSimilarReleases()}; + CHECK(releases.size() == 2); + CHECK(releases[0].id() == release1.getId()); + CHECK(releases[1].id() == release3.getId()); + } } } @@ -1245,6 +1244,8 @@ testDatabaseEmpty(Session& session) CHECK(ClusterType::getAll(session).empty()); CHECK(Release::getAll(session).empty()); CHECK(Track::getAll(session).empty()); + CHECK(TrackList::getAll(session).empty()); + CHECK(User::getAll(session).empty()); } int main() @@ -1287,6 +1288,10 @@ int main() RUN_TEST(testSingleTrackSingleRelease); RUN_TEST(testSingleTrackSingleCluster); + RUN_TEST(testMultipleTracksSingleCluster); + + RUN_TEST(testMultipleTracksSingleClusterSimilarity); + RUN_TEST(testMultipleTracksMultipleClustersSimilarity); RUN_TEST(testSingleTrackSingleReleaseSingleCluster); RUN_TEST(testSingleTrackSingleArtistMultiClusters); @@ -1305,6 +1310,12 @@ int main() RUN_TEST(testSingleStarredRelease); RUN_TEST(testSingleStarredTrack); + RUN_TEST(testSingleTrackList); + RUN_TEST(testSingleTrackListMultipleTrack); + RUN_TEST(testSingleTrackListMultipleTrackSingleCluster); + RUN_TEST(testSingleTrackListMultipleTrackMultiClusters); + RUN_TEST(testMultipleTracksMultipleArtistsMultiClusters); + RUN_TEST(testMultipleTracksMultipleReleasesMultiClusters); } } catch (std::exception& e)