Added Album Artist support
This commit is contained in:
@@ -13,8 +13,9 @@ A [demo](http://lms.demo.poupon.io) instance is available, with the following li
|
|||||||
- Audio transcode for maximum interoperability and low bandwith requirements
|
- Audio transcode for maximum interoperability and low bandwith requirements
|
||||||
- Persistent play queue across sessions
|
- Persistent play queue across sessions
|
||||||
- Subsonic API
|
- Subsonic API
|
||||||
- Multi-value tags
|
- Album artist
|
||||||
- Custom tag support (ex: "mood", "genre", "albummood", "albumgrouping", ...)
|
- Multi-value tags (artists, genres, ...)
|
||||||
|
- Custom tags (ex: "mood", "genre", "albummood", "albumgrouping", ...)
|
||||||
- MusicBrainzID support to handle duplicated artist and release names
|
- MusicBrainzID support to handle duplicated artist and release names
|
||||||
- Playlists (only using Subsonic API for now)
|
- Playlists (only using Subsonic API for now)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@
|
|||||||
<h4 class="media-heading">
|
<h4 class="media-heading">
|
||||||
${name class="Lms-explore-releasename"}
|
${name class="Lms-explore-releasename"}
|
||||||
</h4>
|
</h4>
|
||||||
${<if-has-various-artists>}<h4><small><i>${tr:Lms.Explore.various-artists}</i></small></h4>${</if-has-various-artists>}
|
${<if-has-artist>}<h4><small>${artist class="Lms-explore-artistname"}</small></h4>${</if-has-artist>}
|
||||||
<h4><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></h4>
|
<h4><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></h4>
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h3>${name}</h3>
|
<h3>${name}</h3>
|
||||||
${<if-has-artist>}<h4><i>${artist-name class="Lms-explore-artistname"}</i></h4>${</if-has-artist>}
|
${<if-has-artist>}<h4>${artist class="Lms-explore-artistname"}</h4>${</if-has-artist>}
|
||||||
<h4><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></h4>
|
<h4><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></h4>
|
||||||
${clusters}
|
${clusters}
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ lms_SOURCES = \
|
|||||||
$(srcdir)/database/Cluster.hpp \
|
$(srcdir)/database/Cluster.hpp \
|
||||||
$(srcdir)/database/DatabaseHandler.cpp \
|
$(srcdir)/database/DatabaseHandler.cpp \
|
||||||
$(srcdir)/database/DatabaseHandler.hpp \
|
$(srcdir)/database/DatabaseHandler.hpp \
|
||||||
|
$(srcdir)/database/TrackArtistLink.cpp \
|
||||||
|
$(srcdir)/database/TrackArtistLink.hpp \
|
||||||
$(srcdir)/database/TrackFeatures.cpp \
|
$(srcdir)/database/TrackFeatures.cpp \
|
||||||
$(srcdir)/database/TrackFeatures.hpp \
|
$(srcdir)/database/TrackFeatures.hpp \
|
||||||
$(srcdir)/database/TrackList.cpp \
|
$(srcdir)/database/TrackList.cpp \
|
||||||
|
|||||||
+45
-15
@@ -77,7 +77,7 @@ Artist::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset, b
|
|||||||
std::vector<Artist::pointer>
|
std::vector<Artist::pointer>
|
||||||
Artist::getAllOrphans(Wt::Dbo::Session& session)
|
Artist::getAllOrphans(Wt::Dbo::Session& session)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<Artist::pointer> res = session.query< Wt::Dbo::ptr<Artist> >("SELECT DISTINCT a FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist t_a ON t_a.artist_id = a.id WHERE t.id = t_a.track_id)");
|
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res {session.query<Wt::Dbo::ptr<Artist>>("SELECT DISTINCT a FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id WHERE t.id = t_a_l.track_id)")};
|
||||||
|
|
||||||
return std::vector<pointer>(res.begin(), res.end());
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ getQuery(Wt::Dbo::Session& session,
|
|||||||
|
|
||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
{
|
{
|
||||||
oss << " INNER JOIN track t ON t_a.track_id = t.id INNER JOIN track_artist t_a ON t_a.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
|
oss << " INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
|
||||||
|
|
||||||
WhereClause clusterClause;
|
WhereClause clusterClause;
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ getQuery(Wt::Dbo::Session& session,
|
|||||||
oss << " " << where.get();
|
oss << " " << where.get();
|
||||||
|
|
||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
|
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size();
|
||||||
|
|
||||||
oss << " ORDER BY a.sort_name COLLATE NOCASE";
|
oss << " ORDER BY a.sort_name COLLATE NOCASE";
|
||||||
|
|
||||||
@@ -124,6 +124,13 @@ getQuery(Wt::Dbo::Session& session,
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Artist::pointer>
|
||||||
|
Artist::getByFilter(Wt::Dbo::Session& session, const std::set<IdType>& clusters)
|
||||||
|
{
|
||||||
|
bool more;
|
||||||
|
return getByFilter(session, clusters, {}, {}, {}, more);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Artist::pointer>
|
std::vector<Artist::pointer>
|
||||||
Artist::getByFilter(Wt::Dbo::Session& session,
|
Artist::getByFilter(Wt::Dbo::Session& session,
|
||||||
const std::set<IdType>& clusters,
|
const std::set<IdType>& clusters,
|
||||||
@@ -152,7 +159,7 @@ Artist::getByFilter(Wt::Dbo::Session& session,
|
|||||||
std::vector<Artist::pointer>
|
std::vector<Artist::pointer>
|
||||||
Artist::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> limit)
|
Artist::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> limit)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<Artist::pointer> res = session.query<Artist::pointer>("SELECT a from artist a INNER JOIN track_artist t_a ON t_a.artist_id = a.id INNER JOIN track t ON t.id = t_a.track_id")
|
Wt::Dbo::collection<Artist::pointer> res = session.query<Artist::pointer>("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")
|
||||||
.where("t.file_added > ?").bind(after)
|
.where("t.file_added > ?").bind(after)
|
||||||
.groupBy("a.id")
|
.groupBy("a.id")
|
||||||
.orderBy("t.file_added DESC")
|
.orderBy("t.file_added DESC")
|
||||||
@@ -171,7 +178,8 @@ Artist::getReleases(const std::set<IdType>& clusterIds) const
|
|||||||
WhereClause where;
|
WhereClause where;
|
||||||
|
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "SELECT DISTINCT r FROM release r INNER JOIN artist a ON t_a.artist_id = a.id INNER JOIN track_artist t_a ON t_a.track_id = t.id INNER JOIN track t ON t.release_id = r.id";
|
|
||||||
|
oss << "SELECT DISTINCT r 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())
|
if (!clusterIds.empty())
|
||||||
{
|
{
|
||||||
@@ -190,32 +198,54 @@ Artist::getReleases(const std::set<IdType>& clusterIds) const
|
|||||||
oss << " " << where.get();
|
oss << " " << where.get();
|
||||||
|
|
||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
|
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size();
|
||||||
|
|
||||||
oss << " ORDER BY t.year,r.name";
|
oss << " ORDER BY t.year,r.name";
|
||||||
|
|
||||||
Wt::Dbo::Query<Release::pointer> query = session()->query<Release::pointer>( oss.str() );
|
Wt::Dbo::Query<Release::pointer> query = session()->query<Release::pointer>( oss.str() );
|
||||||
|
|
||||||
for (const std::string& bindArg : where.getBindArgs())
|
for (const std::string& bindArg : where.getBindArgs())
|
||||||
{
|
|
||||||
query.bind(bindArg);
|
query.bind(bindArg);
|
||||||
}
|
|
||||||
|
|
||||||
Wt::Dbo::collection< Wt::Dbo::ptr<Release> > res = query;
|
Wt::Dbo::collection<Wt::Dbo::ptr<Release>> res = query;
|
||||||
|
|
||||||
return std::vector< Wt::Dbo::ptr<Release> > (res.begin(), res.end());
|
return std::vector<Wt::Dbo::ptr<Release>>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Wt::Dbo::ptr<Track>>
|
std::vector<Wt::Dbo::ptr<Track>>
|
||||||
Artist::getTracks() const
|
Artist::getTracks(boost::optional<TrackArtistLink::Type> linkType) const
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {session()->query<Wt::Dbo::ptr<Track>>("SELECT t from Track t INNER JOIN artist a ON t_a.artist_id = a.id INNER JOIN track_artist t_a ON t_a.track_id = t.id INNER JOIN release r ON r.id = t.release_id")
|
assert(self());
|
||||||
|
assert(IdIsValid(self()->id()));
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
auto query {session()->query<Wt::Dbo::ptr<Track>>("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(self()->id())
|
||||||
|
.orderBy("t.year,t.release_id,t.disc_number,t.track_number")};
|
||||||
|
|
||||||
|
if (linkType)
|
||||||
|
query.where("t_a_l.type = ?").bind(*linkType);
|
||||||
|
|
||||||
|
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {query.resultList()};
|
||||||
|
|
||||||
|
return std::vector<Wt::Dbo::ptr<Track>>(tracks.begin(), tracks.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Wt::Dbo::ptr<Track>>
|
||||||
|
Artist::getTracksWithRelease(boost::optional<TrackArtistLink::Type> linkType) const
|
||||||
|
{
|
||||||
|
assert(self());
|
||||||
|
assert(IdIsValid(self()->id()));
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {session()->query<Wt::Dbo::ptr<Track>>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id INNER JOIN release r ON r.id = t.release_id")
|
||||||
.where("a.id = ?").bind(self()->id())
|
.where("a.id = ?").bind(self()->id())
|
||||||
.orderBy("t.year,r.name,t.disc_number,t.track_number")};
|
.orderBy("t.year,r.name,t.disc_number,t.track_number")};
|
||||||
|
|
||||||
return std::vector<Wt::Dbo::ptr<Track>>(tracks.begin(), tracks.end());
|
return std::vector<Wt::Dbo::ptr<Track>>(tracks.begin(), tracks.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<Wt::Dbo::ptr<Track>>
|
std::vector<Wt::Dbo::ptr<Track>>
|
||||||
Artist::getRandomTracks(boost::optional<std::size_t> count) const
|
Artist::getRandomTracks(boost::optional<std::size_t> count) const
|
||||||
{
|
{
|
||||||
@@ -223,7 +253,7 @@ Artist::getRandomTracks(boost::optional<std::size_t> count) const
|
|||||||
assert(IdIsValid(self()->id()));
|
assert(IdIsValid(self()->id()));
|
||||||
assert(session());
|
assert(session());
|
||||||
|
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {session()->query<Wt::Dbo::ptr<Track>>("SELECT t from Track t INNER JOIN artist a ON t_a.artist_id = a.id INNER JOIN track_artist t_a ON t_a.track_id = t.id")
|
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> tracks {session()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
|
||||||
.where("a.id = ?").bind(self()->id())
|
.where("a.id = ?").bind(self()->id())
|
||||||
.orderBy("RANDOM()")
|
.orderBy("RANDOM()")
|
||||||
.limit(count ? static_cast<int>(*count) : -1)};
|
.limit(count ? static_cast<int>(*count) : -1)};
|
||||||
@@ -241,7 +271,7 @@ Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::si
|
|||||||
WhereClause where;
|
WhereClause where;
|
||||||
|
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "SELECT c 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 cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN artist a ON t_a.artist_id = a.id INNER JOIN track_artist t_a ON t_a.track_id = t.id";
|
oss << "SELECT c 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 cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN artist a ON t_a_l.artist_id = a.id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id";
|
||||||
|
|
||||||
where.And(WhereClause("a.id = ?")).bind(std::to_string(self()->id()));
|
where.And(WhereClause("a.id = ?")).bind(std::to_string(self()->id()));
|
||||||
{
|
{
|
||||||
@@ -252,7 +282,7 @@ Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::si
|
|||||||
where.And(clusterClause);
|
where.And(clusterClause);
|
||||||
}
|
}
|
||||||
oss << " " << where.get();
|
oss << " " << where.get();
|
||||||
oss << "GROUP BY c.id ORDER BY COUNT(c.id) DESC";
|
oss << "GROUP BY c.id ORDER BY COUNT(DISTINCT c.id) DESC";
|
||||||
|
|
||||||
Wt::Dbo::Query<Cluster::pointer> query = session()->query<Cluster::pointer>( oss.str() );
|
Wt::Dbo::Query<Cluster::pointer> query = session()->query<Cluster::pointer>( oss.str() );
|
||||||
|
|
||||||
|
|||||||
@@ -27,15 +27,16 @@
|
|||||||
#include <Wt/WDateTime.h>
|
#include <Wt/WDateTime.h>
|
||||||
#include <Wt/Dbo/Dbo.h>
|
#include <Wt/Dbo/Dbo.h>
|
||||||
|
|
||||||
|
#include "TrackArtistLink.hpp"
|
||||||
#include "Types.hpp"
|
#include "Types.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
|
|
||||||
class Track;
|
|
||||||
class Cluster;
|
class Cluster;
|
||||||
class ClusterType;
|
class ClusterType;
|
||||||
class Release;
|
class Release;
|
||||||
|
class Track;
|
||||||
|
|
||||||
class Artist : public Wt::Dbo::Dbo<Artist>
|
class Artist : public Wt::Dbo::Dbo<Artist>
|
||||||
{
|
{
|
||||||
@@ -50,6 +51,8 @@ class Artist : public Wt::Dbo::Dbo<Artist>
|
|||||||
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
|
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
|
||||||
static pointer getById(Wt::Dbo::Session& session, IdType id);
|
static pointer getById(Wt::Dbo::Session& session, IdType id);
|
||||||
static std::vector<pointer> getByName(Wt::Dbo::Session& session, const std::string& name);
|
static std::vector<pointer> getByName(Wt::Dbo::Session& session, const std::string& name);
|
||||||
|
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
||||||
|
const std::set<IdType>& clusters); // at least one track that belongs to these clusters
|
||||||
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
||||||
const std::set<IdType>& clusters, // at least one track that belongs to these clusters
|
const std::set<IdType>& clusters, // at least one track that belongs to these clusters
|
||||||
const std::vector<std::string>& keywords, // name must match all of these keywords
|
const std::vector<std::string>& keywords, // name must match all of these keywords
|
||||||
@@ -65,9 +68,9 @@ class Artist : public Wt::Dbo::Dbo<Artist>
|
|||||||
const std::string& getName(void) const { return _name; }
|
const std::string& getName(void) const { return _name; }
|
||||||
const std::string& getMBID(void) const { return _MBID; }
|
const std::string& getMBID(void) const { return _MBID; }
|
||||||
|
|
||||||
// Get the releases that have at least one track for this artist that belongs to optional cluster filters
|
|
||||||
std::vector<Wt::Dbo::ptr<Release>> getReleases(const std::set<IdType>& clusterIds = std::set<IdType>()) const;
|
std::vector<Wt::Dbo::ptr<Release>> getReleases(const std::set<IdType>& clusterIds = std::set<IdType>()) const;
|
||||||
std::vector<Wt::Dbo::ptr<Track>> getTracks() const;
|
std::vector<Wt::Dbo::ptr<Track>> getTracks(boost::optional<TrackArtistLink::Type> linkType = {}) const;
|
||||||
|
std::vector<Wt::Dbo::ptr<Track>> getTracksWithRelease(boost::optional<TrackArtistLink::Type> linkType = {}) const;
|
||||||
std::vector<Wt::Dbo::ptr<Track>> getRandomTracks(boost::optional<std::size_t> count) const;
|
std::vector<Wt::Dbo::ptr<Track>> getRandomTracks(boost::optional<std::size_t> count) const;
|
||||||
|
|
||||||
// Get the cluster of the tracks made by this artist
|
// Get the cluster of the tracks made by this artist
|
||||||
@@ -89,7 +92,7 @@ class Artist : public Wt::Dbo::Dbo<Artist>
|
|||||||
Wt::Dbo::field(a, _name, "sort_name");
|
Wt::Dbo::field(a, _name, "sort_name");
|
||||||
Wt::Dbo::field(a, _MBID, "mbid");
|
Wt::Dbo::field(a, _MBID, "mbid");
|
||||||
|
|
||||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_artist", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "artist");
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -100,7 +103,7 @@ class Artist : public Wt::Dbo::Dbo<Artist>
|
|||||||
std::string _sortName;
|
std::string _sortName;
|
||||||
std::string _MBID; // Musicbrainz Identifier
|
std::string _MBID; // Musicbrainz Identifier
|
||||||
|
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks of this artist
|
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks; // Tracks involving this artist
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Database
|
} // namespace Database
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ ClusterType::getByName(Wt::Dbo::Session& session, std::string name)
|
|||||||
return session.find<ClusterType>().where("name = ?").bind(name);
|
return session.find<ClusterType>().where("name = ?").bind(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClusterType::pointer
|
||||||
|
ClusterType::getById(Wt::Dbo::Session& session, IdType id)
|
||||||
|
{
|
||||||
|
return session.find<ClusterType>().where("id= ?").bind(id);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<ClusterType::pointer>
|
std::vector<ClusterType::pointer>
|
||||||
ClusterType::getAll(Wt::Dbo::Session& session)
|
ClusterType::getAll(Wt::Dbo::Session& session)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ class ClusterType : public Wt::Dbo::Dbo<ClusterType>
|
|||||||
|
|
||||||
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session);
|
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session);
|
||||||
static pointer getByName(Wt::Dbo::Session& session, std::string name);
|
static pointer getByName(Wt::Dbo::Session& session, std::string name);
|
||||||
|
static pointer getById(Wt::Dbo::Session& session, IdType id);
|
||||||
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
|
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
|
||||||
|
|
||||||
static pointer create(Wt::Dbo::Session& session, std::string name);
|
static pointer create(Wt::Dbo::Session& session, std::string name);
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#include "ScanSettings.hpp"
|
#include "ScanSettings.hpp"
|
||||||
#include "SimilaritySettings.hpp"
|
#include "SimilaritySettings.hpp"
|
||||||
#include "Track.hpp"
|
#include "Track.hpp"
|
||||||
|
#include "TrackArtistLink.hpp"
|
||||||
#include "TrackList.hpp"
|
#include "TrackList.hpp"
|
||||||
#include "TrackFeatures.hpp"
|
#include "TrackFeatures.hpp"
|
||||||
|
|
||||||
@@ -106,6 +107,7 @@ Handler::Handler(Wt::Dbo::SqlConnectionPool& connectionPool)
|
|||||||
_session.mapClass<TrackListEntry>("tracklist_entry");
|
_session.mapClass<TrackListEntry>("tracklist_entry");
|
||||||
_session.mapClass<Release>("release");
|
_session.mapClass<Release>("release");
|
||||||
_session.mapClass<Track>("track");
|
_session.mapClass<Track>("track");
|
||||||
|
_session.mapClass<TrackArtistLink>("track_artist_link");
|
||||||
_session.mapClass<TrackFeatures>("track_features");
|
_session.mapClass<TrackFeatures>("track_features");
|
||||||
|
|
||||||
_session.mapClass<ScanSettings>("scan_settings");
|
_session.mapClass<ScanSettings>("scan_settings");
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ Release::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset,
|
|||||||
{
|
{
|
||||||
Wt::Dbo::collection<pointer> res = session.find<Release>()
|
Wt::Dbo::collection<pointer> res = session.find<Release>()
|
||||||
.offset(offset ? static_cast<int>(*offset) : -1)
|
.offset(offset ? static_cast<int>(*offset) : -1)
|
||||||
.limit(size ? static_cast<int>(*size) : - 1)
|
.limit(size ? static_cast<int>(*size) : -1)
|
||||||
.orderBy("name COLLATE NOCASE");
|
.orderBy("name COLLATE NOCASE");
|
||||||
|
|
||||||
return std::vector<pointer>(res.begin(), res.end());
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
@@ -83,7 +83,7 @@ std::vector<Release::pointer>
|
|||||||
Release::getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> size)
|
Release::getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> size)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<pointer> res = session.find<Release>()
|
Wt::Dbo::collection<pointer> res = session.find<Release>()
|
||||||
.limit(size ? static_cast<int>(*size) : - 1)
|
.limit(size ? static_cast<int>(*size) : -1)
|
||||||
.orderBy("RANDOM()");
|
.orderBy("RANDOM()");
|
||||||
|
|
||||||
return std::vector<pointer>(res.begin(), res.end());
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
@@ -183,6 +183,18 @@ Release::getByFilter(Wt::Dbo::Session& session,
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<std::size_t>
|
||||||
|
Release::getTotalTrackNumber(void) const
|
||||||
|
{
|
||||||
|
return (_totalTrackNumber > 0) ? boost::make_optional<std::size_t>(_totalTrackNumber) : boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::size_t>
|
||||||
|
Release::getTotalDiscNumber(void) const
|
||||||
|
{
|
||||||
|
return (_totalDiscNumber > 0) ? boost::make_optional<std::size_t>(_totalDiscNumber) : boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
boost::optional<int>
|
boost::optional<int>
|
||||||
Release::getReleaseYear(bool original) const
|
Release::getReleaseYear(bool original) const
|
||||||
{
|
{
|
||||||
@@ -249,16 +261,16 @@ Release::getCopyrightURL() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Wt::Dbo::ptr<Artist>>
|
std::vector<Wt::Dbo::ptr<Artist>>
|
||||||
Release::getArtists() const
|
Release::getArtists(TrackArtistLink::Type linkType) const
|
||||||
{
|
{
|
||||||
assert(self());
|
assert(self());
|
||||||
assert(IdIsValid(self()->id()));
|
assert(IdIsValid(self()->id()));
|
||||||
assert(session());
|
assert(session());
|
||||||
|
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session()->query<Wt::Dbo::ptr<Artist>>(
|
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session()->query<Wt::Dbo::ptr<Artist>>(
|
||||||
"SELECT DISTINCT a FROM artist a INNER JOIN release r ON t.id = t_a.track_id INNER JOIN track_artist t_a ON t_a.artist_id = a.id INNER JOIN track t ON t.release_id = r.id")
|
"SELECT DISTINCT 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 release r ON r.id = t.release_id")
|
||||||
.where("r.id = ?")
|
.where("r.id = ?").bind(self()->id())
|
||||||
.bind(id());
|
.where("t_a_l.type = ?").bind(linkType);
|
||||||
|
|
||||||
return std::vector<Wt::Dbo::ptr<Artist>>(res.begin(), res.end());
|
return std::vector<Wt::Dbo::ptr<Artist>>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <Wt/Dbo/WtSqlTraits.h>
|
#include <Wt/Dbo/WtSqlTraits.h>
|
||||||
|
|
||||||
|
#include "TrackArtistLink.hpp"
|
||||||
#include "Types.hpp"
|
#include "Types.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
@@ -76,13 +77,20 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
boost::optional<std::string> getCopyright() const;
|
boost::optional<std::string> getCopyright() const;
|
||||||
boost::optional<std::string> getCopyrightURL() const;
|
boost::optional<std::string> getCopyrightURL() const;
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
void setTotalDiscNumber(std::size_t num) { _totalDiscNumber = static_cast<int>(num); }
|
||||||
|
void setTotalTrackNumber(std::size_t num) { _totalTrackNumber = static_cast<int>(num); }
|
||||||
|
|
||||||
// Accessors
|
// Accessors
|
||||||
std::string getName() const { return _name; }
|
std::string getName() const { return _name; }
|
||||||
std::string getMBID() const { return _MBID; }
|
std::string getMBID() const { return _MBID; }
|
||||||
|
boost::optional<std::size_t> getTotalTrackNumber() const;
|
||||||
|
boost::optional<std::size_t> getTotalDiscNumber() const;
|
||||||
std::chrono::milliseconds getDuration() const;
|
std::chrono::milliseconds getDuration() const;
|
||||||
|
|
||||||
// Get the artists of this release
|
// Get the artists of this release
|
||||||
std::vector<Wt::Dbo::ptr<Artist> > getArtists() const;
|
std::vector<Wt::Dbo::ptr<Artist> > getArtists(TrackArtistLink::Type type = TrackArtistLink::Type::Artist) const;
|
||||||
|
std::vector<Wt::Dbo::ptr<Artist> > getReleaseArtists() const { return getArtists(TrackArtistLink::Type::ReleaseArtist); }
|
||||||
bool hasVariousArtists() const;
|
bool hasVariousArtists() const;
|
||||||
|
|
||||||
void setMBID(std::string mbid) { _MBID = mbid; }
|
void setMBID(std::string mbid) { _MBID = mbid; }
|
||||||
@@ -92,17 +100,21 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
{
|
{
|
||||||
Wt::Dbo::field(a, _name, "name");
|
Wt::Dbo::field(a, _name, "name");
|
||||||
Wt::Dbo::field(a, _MBID, "mbid");
|
Wt::Dbo::field(a, _MBID, "mbid");
|
||||||
|
Wt::Dbo::field(a, _totalDiscNumber, "total_disc_number");
|
||||||
|
Wt::Dbo::field(a, _totalTrackNumber, "total_track_number");
|
||||||
|
|
||||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const std::size_t _maxNameLength = 128;
|
static const std::size_t _maxNameLength {128};
|
||||||
|
|
||||||
std::string _name;
|
std::string _name;
|
||||||
std::string _MBID;
|
std::string _MBID;
|
||||||
|
int _totalDiscNumber {};
|
||||||
|
int _totalTrackNumber {};
|
||||||
|
|
||||||
Wt::Dbo::collection< Wt::Dbo::ptr<Track> > _tracks; // Tracks in the release
|
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Database
|
} // namespace Database
|
||||||
|
|||||||
+34
-22
@@ -37,12 +37,13 @@ _filePath( p.string() )
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::Dbo::collection< Track::pointer >
|
std::vector<Track::pointer>
|
||||||
Track::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> limit)
|
Track::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> limit)
|
||||||
{
|
{
|
||||||
int size {limit ? static_cast<int>(*limit) : -1};
|
Wt::Dbo::collection<Track::pointer> res {session.find<Track>()
|
||||||
|
.limit(limit ? static_cast<int>(*limit) : -1)};
|
||||||
|
|
||||||
return session.find<Track>().limit(size);
|
return std::vector<Track::pointer>(std::cbegin(res), std::cend(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Track::pointer>
|
std::vector<Track::pointer>
|
||||||
@@ -229,15 +230,24 @@ Track::getByFilter(Wt::Dbo::Session& session,
|
|||||||
{
|
{
|
||||||
bool moreResults;
|
bool moreResults;
|
||||||
|
|
||||||
return getByFilter(session, clusters, std::vector<std::string>{}, -1, -1, moreResults);
|
return getByFilter(session,
|
||||||
|
clusters,
|
||||||
|
std::vector<std::string> {},
|
||||||
|
boost::optional<std::size_t> {},
|
||||||
|
boost::optional<std::size_t> {},
|
||||||
|
moreResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Track::setArtists(const std::vector<Wt::Dbo::ptr<Artist>>& artists)
|
Track::clearArtistLinks()
|
||||||
{
|
{
|
||||||
_artists.clear();
|
_trackArtistLinks.clear();
|
||||||
for (const Wt::Dbo::ptr<Artist>& artist : artists)
|
}
|
||||||
_artists.insert(artist);
|
|
||||||
|
void
|
||||||
|
Track::addArtistLink(const Wt::Dbo::ptr<TrackArtistLink>& artistLink)
|
||||||
|
{
|
||||||
|
_trackArtistLinks.insert(artistLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -260,24 +270,12 @@ Track::getTrackNumber(void) const
|
|||||||
return (_trackNumber > 0) ? boost::make_optional<std::size_t>(_trackNumber) : boost::none;
|
return (_trackNumber > 0) ? boost::make_optional<std::size_t>(_trackNumber) : boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<std::size_t>
|
|
||||||
Track::getTotalTrackNumber(void) const
|
|
||||||
{
|
|
||||||
return (_totalTrackNumber > 0) ? boost::make_optional<std::size_t>(_totalTrackNumber) : boost::none;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::optional<std::size_t>
|
boost::optional<std::size_t>
|
||||||
Track::getDiscNumber(void) const
|
Track::getDiscNumber(void) const
|
||||||
{
|
{
|
||||||
return (_discNumber > 0) ? boost::make_optional<std::size_t>(_discNumber) : boost::none;
|
return (_discNumber > 0) ? boost::make_optional<std::size_t>(_discNumber) : boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<std::size_t>
|
|
||||||
Track::getTotalDiscNumber(void) const
|
|
||||||
{
|
|
||||||
return (_totalDiscNumber > 0) ? boost::make_optional<std::size_t>(_totalDiscNumber) : boost::none;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::optional<int>
|
boost::optional<int>
|
||||||
Track::getYear() const
|
Track::getYear() const
|
||||||
{
|
{
|
||||||
@@ -303,9 +301,23 @@ Track::getCopyrightURL() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Wt::Dbo::ptr<Artist>>
|
std::vector<Wt::Dbo::ptr<Artist>>
|
||||||
Track::getArtists() const
|
Track::getArtists(TrackArtistLink::Type type) const
|
||||||
{
|
{
|
||||||
return std::vector<Wt::Dbo::ptr<Artist>>(_artists.begin(), _artists.end());
|
assert(self());
|
||||||
|
assert(IdIsValid(self()->id()));
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> artists {session()->query<Artist::pointer>("SELECT a from artist a INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id INNER JOIN track t ON t.id = t_a_l.track_id")
|
||||||
|
.where("t.id = ?").bind(self()->id())
|
||||||
|
.where("t_a_l.type = ?").bind(type)};
|
||||||
|
|
||||||
|
return std::vector<Wt::Dbo::ptr<Artist>>(artists.begin(), artists.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Wt::Dbo::ptr<TrackArtistLink>>
|
||||||
|
Track::getArtistLinks() const
|
||||||
|
{
|
||||||
|
return std::vector<Wt::Dbo::ptr<TrackArtistLink>>(_trackArtistLinks.begin(), _trackArtistLinks.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::Dbo::ptr<TrackFeatures>
|
Wt::Dbo::ptr<TrackFeatures>
|
||||||
|
|||||||
+8
-13
@@ -29,6 +29,7 @@
|
|||||||
#include <Wt/Dbo/Dbo.h>
|
#include <Wt/Dbo/Dbo.h>
|
||||||
#include <Wt/WDateTime.h>
|
#include <Wt/WDateTime.h>
|
||||||
|
|
||||||
|
#include "TrackArtistLink.hpp"
|
||||||
#include "Types.hpp"
|
#include "Types.hpp"
|
||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
@@ -63,7 +64,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
boost::optional<std::size_t> size,
|
boost::optional<std::size_t> size,
|
||||||
bool& moreExpected);
|
bool& moreExpected);
|
||||||
|
|
||||||
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {});
|
static std::vector<pointer> getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {});
|
||||||
static std::vector<pointer> getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {});
|
static std::vector<pointer> getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {});
|
||||||
static std::vector<IdType> getAllIds(Wt::Dbo::Session& session); // nested transaction
|
static std::vector<IdType> getAllIds(Wt::Dbo::Session& session); // nested transaction
|
||||||
static std::vector<boost::filesystem::path> getAllPaths(Wt::Dbo::Session& session); // nested transaction
|
static std::vector<boost::filesystem::path> getAllPaths(Wt::Dbo::Session& session); // nested transaction
|
||||||
@@ -79,9 +80,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
// Accessors
|
// Accessors
|
||||||
void setScanVersion(std::size_t version) { _scanVersion = version; }
|
void setScanVersion(std::size_t version) { _scanVersion = version; }
|
||||||
void setTrackNumber(int num) { _trackNumber = num; }
|
void setTrackNumber(int num) { _trackNumber = num; }
|
||||||
void setTotalTrackNumber(int num) { _totalTrackNumber = num; }
|
|
||||||
void setDiscNumber(int num) { _discNumber = num; }
|
void setDiscNumber(int num) { _discNumber = num; }
|
||||||
void setTotalDiscNumber(int num) { _totalDiscNumber = num; }
|
|
||||||
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
|
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
|
||||||
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
|
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
|
||||||
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
|
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
|
||||||
@@ -93,16 +92,15 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
void setMBID(const std::string& MBID) { _MBID = MBID; }
|
void setMBID(const std::string& MBID) { _MBID = MBID; }
|
||||||
void setCopyright(const std::string& copyright) { _copyright = std::string(copyright, 0, _maxCopyrightLength); }
|
void setCopyright(const std::string& copyright) { _copyright = std::string(copyright, 0, _maxCopyrightLength); }
|
||||||
void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
|
void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
|
||||||
void setArtists(const std::vector<Wt::Dbo::ptr<Artist>>& artists);
|
void clearArtistLinks();
|
||||||
|
void addArtistLink(const Wt::Dbo::ptr<TrackArtistLink>& artistLink);
|
||||||
void setRelease(Wt::Dbo::ptr<Release> release) { _release = release; }
|
void setRelease(Wt::Dbo::ptr<Release> release) { _release = release; }
|
||||||
void setClusters(const std::vector<Wt::Dbo::ptr<Cluster>>& clusters );
|
void setClusters(const std::vector<Wt::Dbo::ptr<Cluster>>& clusters );
|
||||||
void setFeatures(const Wt::Dbo::ptr<TrackFeatures>& features);
|
void setFeatures(const Wt::Dbo::ptr<TrackFeatures>& features);
|
||||||
|
|
||||||
std::size_t getScanVersion() const { return _scanVersion; }
|
std::size_t getScanVersion() const { return _scanVersion; }
|
||||||
boost::optional<std::size_t> getTrackNumber() const;
|
boost::optional<std::size_t> getTrackNumber() const;
|
||||||
boost::optional<std::size_t> getTotalTrackNumber() const;
|
|
||||||
boost::optional<std::size_t> getDiscNumber() const;
|
boost::optional<std::size_t> getDiscNumber() const;
|
||||||
boost::optional<std::size_t> getTotalDiscNumber() const;
|
|
||||||
std::string getName() const { return _name; }
|
std::string getName() const { return _name; }
|
||||||
boost::filesystem::path getPath() const { return _filePath; }
|
boost::filesystem::path getPath() const { return _filePath; }
|
||||||
std::chrono::milliseconds getDuration() const { return _duration; }
|
std::chrono::milliseconds getDuration() const { return _duration; }
|
||||||
@@ -115,7 +113,8 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
const std::string& getMBID() const { return _MBID; }
|
const std::string& getMBID() const { return _MBID; }
|
||||||
boost::optional<std::string> getCopyright() const;
|
boost::optional<std::string> getCopyright() const;
|
||||||
boost::optional<std::string> getCopyrightURL() const;
|
boost::optional<std::string> getCopyrightURL() const;
|
||||||
std::vector<Wt::Dbo::ptr<Artist>> getArtists() const;
|
std::vector<Wt::Dbo::ptr<Artist>> getArtists(TrackArtistLink::Type type = {TrackArtistLink::Type::Artist}) const;
|
||||||
|
std::vector<Wt::Dbo::ptr<TrackArtistLink>> getArtistLinks() const;
|
||||||
Wt::Dbo::ptr<Release> getRelease() const { return _release; }
|
Wt::Dbo::ptr<Release> getRelease() const { return _release; }
|
||||||
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
|
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
|
||||||
bool hasTrackFeatures() const;
|
bool hasTrackFeatures() const;
|
||||||
@@ -128,9 +127,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
{
|
{
|
||||||
Wt::Dbo::field(a, _scanVersion, "scan_version");
|
Wt::Dbo::field(a, _scanVersion, "scan_version");
|
||||||
Wt::Dbo::field(a, _trackNumber, "track_number");
|
Wt::Dbo::field(a, _trackNumber, "track_number");
|
||||||
Wt::Dbo::field(a, _totalTrackNumber, "total_track_number");
|
|
||||||
Wt::Dbo::field(a, _discNumber, "disc_number");
|
Wt::Dbo::field(a, _discNumber, "disc_number");
|
||||||
Wt::Dbo::field(a, _totalDiscNumber, "total_disc_number");
|
|
||||||
Wt::Dbo::field(a, _name, "name");
|
Wt::Dbo::field(a, _name, "name");
|
||||||
Wt::Dbo::field(a, _duration, "duration");
|
Wt::Dbo::field(a, _duration, "duration");
|
||||||
Wt::Dbo::field(a, _year, "year");
|
Wt::Dbo::field(a, _year, "year");
|
||||||
@@ -144,7 +141,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
Wt::Dbo::field(a, _copyright, "copyright");
|
Wt::Dbo::field(a, _copyright, "copyright");
|
||||||
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
|
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
|
||||||
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||||
Wt::Dbo::hasMany(a, _artists, Wt::Dbo::ManyToMany, "track_artist", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
|
||||||
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
||||||
Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track");
|
Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track");
|
||||||
Wt::Dbo::hasOne(a, _trackFeatures);
|
Wt::Dbo::hasOne(a, _trackFeatures);
|
||||||
@@ -158,9 +155,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
|
|
||||||
int _scanVersion = 0;
|
int _scanVersion = 0;
|
||||||
int _trackNumber = 0;
|
int _trackNumber = 0;
|
||||||
int _totalTrackNumber = 0;
|
|
||||||
int _discNumber = 0;
|
int _discNumber = 0;
|
||||||
int _totalDiscNumber = 0;
|
|
||||||
std::string _name;
|
std::string _name;
|
||||||
std::string _artistName;
|
std::string _artistName;
|
||||||
std::string _releaseName;
|
std::string _releaseName;
|
||||||
@@ -177,7 +172,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
std::string _copyrightURL;
|
std::string _copyrightURL;
|
||||||
|
|
||||||
Wt::Dbo::ptr<Release> _release;
|
Wt::Dbo::ptr<Release> _release;
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> _artists;
|
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> _clusters;
|
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> _clusters;
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> _playlistEntries;
|
Wt::Dbo::collection<Wt::Dbo::ptr<TrackListEntry>> _playlistEntries;
|
||||||
Wt::Dbo::weak_ptr<TrackFeatures> _trackFeatures;
|
Wt::Dbo::weak_ptr<TrackFeatures> _trackFeatures;
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2016 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TrackArtistLink.hpp"
|
||||||
|
|
||||||
|
#include "Artist.hpp"
|
||||||
|
#include "Track.hpp"
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
TrackArtistLink::TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, Type type)
|
||||||
|
: _type {type},
|
||||||
|
_track {track},
|
||||||
|
_artist {artist}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackArtistLink::pointer
|
||||||
|
TrackArtistLink::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist,Type type)
|
||||||
|
{
|
||||||
|
return session.add(std::make_unique<TrackArtistLink>(track, artist, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2016 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Wt/Dbo/Dbo.h>
|
||||||
|
|
||||||
|
#include "Types.hpp"
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
class Artist;
|
||||||
|
class Track;
|
||||||
|
|
||||||
|
class TrackArtistLink
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class Type
|
||||||
|
{
|
||||||
|
Artist, // regular artist
|
||||||
|
Arranger,
|
||||||
|
Composer,
|
||||||
|
Conductor,
|
||||||
|
Lyricist,
|
||||||
|
Mixer,
|
||||||
|
Performer,
|
||||||
|
Producer,
|
||||||
|
ReleaseArtist,
|
||||||
|
Remixer,
|
||||||
|
Writer,
|
||||||
|
};
|
||||||
|
|
||||||
|
using pointer = Wt::Dbo::ptr<TrackArtistLink>;
|
||||||
|
|
||||||
|
TrackArtistLink() = default;
|
||||||
|
TrackArtistLink(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist, Type type);
|
||||||
|
|
||||||
|
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Artist> artist,Type type);
|
||||||
|
|
||||||
|
Wt::Dbo::ptr<Track> getTrack() const { return _track; }
|
||||||
|
Wt::Dbo::ptr<Artist> getArtist() const { return _artist; }
|
||||||
|
Type getType() const { return _type; }
|
||||||
|
|
||||||
|
template<class Action>
|
||||||
|
void persist(Action& a)
|
||||||
|
{
|
||||||
|
Wt::Dbo::field(a, _type, "type");
|
||||||
|
Wt::Dbo::field(a, _type, "name");
|
||||||
|
|
||||||
|
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
|
||||||
|
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Type _type;
|
||||||
|
std::string _name;
|
||||||
|
|
||||||
|
Wt::Dbo::ptr<Track> _track;
|
||||||
|
Wt::Dbo::ptr<Artist> _artist;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ TrackList::getTopArtists(std::size_t limit) const
|
|||||||
assert(session());
|
assert(session());
|
||||||
assert(IdIsValid(self()->id()));
|
assert(IdIsValid(self()->id()));
|
||||||
|
|
||||||
Wt::Dbo::collection<Artist::pointer> res = session()->query<Artist::pointer>("SELECT a from artist a INNER JOIN track t ON t.id = t_a.track_id INNER JOIN track_artist t_a ON t_a.artist_id = a.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")
|
Wt::Dbo::collection<Artist::pointer> res = session()->query<Artist::pointer>("SELECT a from artist a INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id INNER JOIN 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 = ?").bind(self()->id())
|
.where("p.id = ?").bind(self()->id())
|
||||||
.groupBy("a.id")
|
.groupBy("a.id")
|
||||||
.orderBy("COUNT(a.id) DESC")
|
.orderBy("COUNT(a.id) DESC")
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ std::vector<std::string> generateWtConfig(std::string execPath)
|
|||||||
args.push_back("--http-address=" + Config::instance().getString("listen-addr", "0.0.0.0"));
|
args.push_back("--http-address=" + Config::instance().getString("listen-addr", "0.0.0.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args.push_back("--gdb");
|
||||||
|
|
||||||
// Generate the wt_config.xml file
|
// Generate the wt_config.xml file
|
||||||
boost::property_tree::ptree pt;
|
boost::property_tree::ptree pt;
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ isPathInParentPath(const boost::filesystem::path& path, const boost::filesystem:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Artist::pointer>
|
std::vector<Artist::pointer>
|
||||||
getArtists(Wt::Dbo::Session& session, const std::vector<MetaData::Artist>& artistsInfo)
|
getOrCreateArtists(Wt::Dbo::Session& session, const std::vector<MetaData::Artist>& artistsInfo)
|
||||||
{
|
{
|
||||||
std::vector<Artist::pointer> artists;
|
std::vector<Artist::pointer> artists;
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ getArtists(Wt::Dbo::Session& session, const std::vector<MetaData::Artist>& artis
|
|||||||
}
|
}
|
||||||
|
|
||||||
Release::pointer
|
Release::pointer
|
||||||
getRelease(Wt::Dbo::Session& session, const MetaData::Album& album)
|
getOrCreateRelease(Wt::Dbo::Session& session, const MetaData::Album& album)
|
||||||
{
|
{
|
||||||
Release::pointer release;
|
Release::pointer release;
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ getRelease(Wt::Dbo::Session& session, const MetaData::Album& album)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Cluster::pointer>
|
std::vector<Cluster::pointer>
|
||||||
getClusters(Wt::Dbo::Session& session, const MetaData::Clusters& clustersNames)
|
getOrCreateClusters(Wt::Dbo::Session& session, const MetaData::Clusters& clustersNames)
|
||||||
{
|
{
|
||||||
std::vector< Cluster::pointer > clusters;
|
std::vector< Cluster::pointer > clusters;
|
||||||
|
|
||||||
@@ -465,15 +465,18 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ***** Clusters
|
// ***** Clusters
|
||||||
std::vector<Cluster::pointer> clusters {getClusters(_db.getSession(), trackInfo->clusters)};
|
std::vector<Cluster::pointer> clusters {getOrCreateClusters(_db.getSession(), trackInfo->clusters)};
|
||||||
|
|
||||||
// ***** Artists
|
// ***** Artists
|
||||||
std::vector<Artist::pointer> artists {getArtists(_db.getSession(), trackInfo->artists)};
|
std::vector<Artist::pointer> artists {getOrCreateArtists(_db.getSession(), trackInfo->artists)};
|
||||||
|
|
||||||
|
// ***** Release artists
|
||||||
|
std::vector<Artist::pointer> releaseArtists {getOrCreateArtists(_db.getSession(), trackInfo->albumArtists)};
|
||||||
|
|
||||||
// ***** Release
|
// ***** Release
|
||||||
Release::pointer release;
|
Release::pointer release;
|
||||||
if (trackInfo->album)
|
if (trackInfo->album)
|
||||||
release = getRelease(_db.getSession(), *trackInfo->album);
|
release = getOrCreateRelease(_db.getSession(), *trackInfo->album);
|
||||||
|
|
||||||
// If file already exist, update data
|
// If file already exist, update data
|
||||||
// Otherwise, create it
|
// Otherwise, create it
|
||||||
@@ -493,11 +496,25 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
|
|||||||
stats.updates++;
|
stats.updates++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Release related data
|
||||||
|
if (release)
|
||||||
|
{
|
||||||
|
release.modify()->setTotalTrackNumber(trackInfo->totalTrack ? *trackInfo->totalTrack : 0);
|
||||||
|
release.modify()->setTotalDiscNumber(trackInfo->totalDisc ? *trackInfo->totalDisc : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track related data
|
||||||
assert(track);
|
assert(track);
|
||||||
|
|
||||||
|
track.modify()->clearArtistLinks();
|
||||||
|
for (const auto& artist : artists)
|
||||||
|
track.modify()->addArtistLink(Database::TrackArtistLink::create(_db.getSession(), track, artist, Database::TrackArtistLink::Type::Artist));
|
||||||
|
|
||||||
|
for (const auto& releaseArtist : releaseArtists)
|
||||||
|
track.modify()->addArtistLink(Database::TrackArtistLink::create(_db.getSession(), track, releaseArtist, Database::TrackArtistLink::Type::ReleaseArtist));
|
||||||
|
|
||||||
track.modify()->setScanVersion(_scanVersion);
|
track.modify()->setScanVersion(_scanVersion);
|
||||||
track.modify()->setChecksum(checksum);
|
track.modify()->setChecksum(checksum);
|
||||||
track.modify()->setArtists(artists);
|
|
||||||
track.modify()->setRelease(release);
|
track.modify()->setRelease(release);
|
||||||
track.modify()->setClusters(clusters);
|
track.modify()->setClusters(clusters);
|
||||||
track.modify()->setLastWriteTime(lastWriteTime);
|
track.modify()->setLastWriteTime(lastWriteTime);
|
||||||
@@ -505,9 +522,7 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
|
|||||||
track.modify()->setDuration(trackInfo->duration);
|
track.modify()->setDuration(trackInfo->duration);
|
||||||
track.modify()->setAddedTime(Wt::WLocalDateTime::currentServerDateTime().toUTC());
|
track.modify()->setAddedTime(Wt::WLocalDateTime::currentServerDateTime().toUTC());
|
||||||
track.modify()->setTrackNumber(trackInfo->trackNumber ? *trackInfo->trackNumber : 0);
|
track.modify()->setTrackNumber(trackInfo->trackNumber ? *trackInfo->trackNumber : 0);
|
||||||
track.modify()->setTotalTrackNumber(trackInfo->totalTrack ? *trackInfo->totalTrack : 0);
|
|
||||||
track.modify()->setDiscNumber(trackInfo->discNumber ? *trackInfo->discNumber : 0);
|
track.modify()->setDiscNumber(trackInfo->discNumber ? *trackInfo->discNumber : 0);
|
||||||
track.modify()->setTotalDiscNumber(trackInfo->totalDisc ? *trackInfo->totalDisc : 0);
|
|
||||||
track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0);
|
track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0);
|
||||||
track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0);
|
track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0);
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ Artist::refresh()
|
|||||||
Wt::WContainerWidget* releasesContainer = t->bindNew<Wt::WContainerWidget>("releases");
|
Wt::WContainerWidget* releasesContainer = t->bindNew<Wt::WContainerWidget>("releases");
|
||||||
|
|
||||||
auto releases = artist->getReleases(_filters->getClusterIds());
|
auto releases = artist->getReleases(_filters->getClusterIds());
|
||||||
|
|
||||||
for (auto release : releases)
|
for (auto release : releases)
|
||||||
{
|
{
|
||||||
auto releaseId = release.id();
|
auto releaseId = release.id();
|
||||||
@@ -139,8 +140,27 @@ Artist::refresh()
|
|||||||
|
|
||||||
entry->bindWidget("name", LmsApplication::createReleaseAnchor(release));
|
entry->bindWidget("name", LmsApplication::createReleaseAnchor(release));
|
||||||
|
|
||||||
if (release->hasVariousArtists())
|
auto artists {release->getReleaseArtists()};
|
||||||
entry->setCondition("if-has-various-artists", true);
|
LMS_LOG(UI, DEBUG) << "Found " << artists.size() << " release artists";
|
||||||
|
|
||||||
|
for (auto artist : artists)
|
||||||
|
LMS_LOG(UI, DEBUG) << "\tArtist = '" << artist->getName() << "'";
|
||||||
|
|
||||||
|
if (artists.empty())
|
||||||
|
artists = release->getArtists();
|
||||||
|
|
||||||
|
bool isSameArtist {(std::find(std::cbegin(artists), std::cend(artists), artist) != artists.end())};
|
||||||
|
|
||||||
|
if (artists.size() > 1)
|
||||||
|
{
|
||||||
|
entry->setCondition("if-has-artist", true);
|
||||||
|
entry->bindNew<Wt::WText>("artist", Wt::WString::tr("Lms.Explore.various-artists"));
|
||||||
|
}
|
||||||
|
else if (artists.size() == 1 && !isSameArtist)
|
||||||
|
{
|
||||||
|
entry->setCondition("if-has-artist", true);
|
||||||
|
entry->bindWidget("artist", LmsApplication::createArtistAnchor(artists.front()));
|
||||||
|
}
|
||||||
|
|
||||||
boost::optional<int> year = release->getReleaseYear();
|
boost::optional<int> year = release->getReleaseYear();
|
||||||
if (year)
|
if (year)
|
||||||
|
|||||||
@@ -44,7 +44,10 @@ ReleaseLink::ReleaseLink(Database::Release::pointer release)
|
|||||||
cover->setWidth(48);
|
cover->setWidth(48);
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
|
|
||||||
auto artists = release->getArtists();
|
auto artists = release->getReleaseArtists();
|
||||||
|
if (artists.empty())
|
||||||
|
artists = release->getArtists();
|
||||||
|
|
||||||
if (artists.size() > 1)
|
if (artists.size() > 1)
|
||||||
{
|
{
|
||||||
setCondition("if-has-artist", true);
|
setCondition("if-has-artist", true);
|
||||||
|
|||||||
@@ -96,16 +96,21 @@ Release::refresh()
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto artists {release->getArtists()};
|
std::vector<Wt::Dbo::ptr<Database::Artist>> artists;
|
||||||
|
|
||||||
|
artists = release->getReleaseArtists();
|
||||||
|
if (artists.empty())
|
||||||
|
artists = release->getArtists();
|
||||||
|
|
||||||
if (artists.size() > 1)
|
if (artists.size() > 1)
|
||||||
{
|
{
|
||||||
t->setCondition("if-has-artist", true);
|
t->setCondition("if-has-artist", true);
|
||||||
t->bindNew<Wt::WText>("artist-name", Wt::WString::tr("Lms.Explore.various-artists"));
|
t->bindNew<Wt::WText>("artist", Wt::WString::tr("Lms.Explore.various-artists"));
|
||||||
}
|
}
|
||||||
else if (artists.size() == 1)
|
else if (artists.size() == 1)
|
||||||
{
|
{
|
||||||
t->setCondition("if-has-artist", true);
|
t->setCondition("if-has-artist", true);
|
||||||
t->bindWidget("artist-name", LmsApplication::createArtistAnchor(artists.front()));
|
t->bindWidget("artist", LmsApplication::createArtistAnchor(artists.front()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +186,7 @@ Release::refresh()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto discNumber {track->getDiscNumber()};
|
auto discNumber {track->getDiscNumber()};
|
||||||
auto totalDiscNumber {track->getTotalDiscNumber()};
|
auto totalDiscNumber {release->getTotalDiscNumber()};
|
||||||
if (discNumber && totalDiscNumber && *totalDiscNumber > 1)
|
if (discNumber && totalDiscNumber && *totalDiscNumber > 1)
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-disc-number", true);
|
entry->setCondition("if-has-disc-number", true);
|
||||||
|
|||||||
@@ -96,7 +96,10 @@ Releases::addSome()
|
|||||||
|
|
||||||
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
|
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
|
||||||
|
|
||||||
auto artists = release->getArtists();
|
auto artists = release->getReleaseArtists();
|
||||||
|
if (artists.empty())
|
||||||
|
artists = release->getArtists();
|
||||||
|
|
||||||
if (artists.size() > 1)
|
if (artists.size() > 1)
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artist", true);
|
entry->setCondition("if-has-artist", true);
|
||||||
|
|||||||
+23
-5
@@ -1,14 +1,32 @@
|
|||||||
|
|
||||||
TESTS = som-test
|
TESTS = som database
|
||||||
|
|
||||||
check_PROGRAMS = som-test
|
check_PROGRAMS = som database
|
||||||
|
|
||||||
som_test_SOURCES = \
|
som_SOURCES = \
|
||||||
$(srcdir)/som-test/SomTest.cpp \
|
$(srcdir)/som/SomTest.cpp \
|
||||||
$(top_srcdir)/src/similarity/features/som/DataNormalizer.cpp \
|
$(top_srcdir)/src/similarity/features/som/DataNormalizer.cpp \
|
||||||
$(top_srcdir)/src/similarity/features/som/Network.cpp
|
$(top_srcdir)/src/similarity/features/som/Network.cpp
|
||||||
|
|
||||||
som_test_CXXFLAGS=-std=c++14 -Wall -I${top_srcdir}/src/ -I${top_srcdir}/src/similarity/features/som/
|
som_CXXFLAGS=-std=c++14 -Wall -I${top_srcdir}/src/ -I${top_srcdir}/src/similarity/features/som/
|
||||||
|
|
||||||
|
|
||||||
|
database_SOURCES = \
|
||||||
|
$(srcdir)/database/DatabaseTest.cpp \
|
||||||
|
$(top_srcdir)/src/database/Artist.cpp \
|
||||||
|
$(top_srcdir)/src/database/Cluster.cpp \
|
||||||
|
$(top_srcdir)/src/database/DatabaseHandler.cpp \
|
||||||
|
$(top_srcdir)/src/database/TrackArtistLink.cpp \
|
||||||
|
$(top_srcdir)/src/database/TrackFeatures.cpp \
|
||||||
|
$(top_srcdir)/src/database/TrackList.cpp \
|
||||||
|
$(top_srcdir)/src/database/Release.cpp \
|
||||||
|
$(top_srcdir)/src/database/ScanSettings.cpp \
|
||||||
|
$(top_srcdir)/src/database/SimilaritySettings.cpp \
|
||||||
|
$(top_srcdir)/src/database/SqlQuery.cpp \
|
||||||
|
$(top_srcdir)/src/database/Track.cpp \
|
||||||
|
$(top_srcdir)/src/database/User.cpp \
|
||||||
|
$(top_srcdir)/src/utils/Logger.cpp \
|
||||||
|
$(top_srcdir)/src/utils/Utils.cpp
|
||||||
|
|
||||||
|
database_CXXFLAGS=-std=c++14 -Wall -I${top_srcdir}/src/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,948 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
#include "database/Artist.hpp"
|
||||||
|
#include "database/Cluster.hpp"
|
||||||
|
#include "database/DatabaseHandler.hpp"
|
||||||
|
#include "database/TrackList.hpp"
|
||||||
|
#include "database/Release.hpp"
|
||||||
|
#include "database/Track.hpp"
|
||||||
|
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
|
class ScopedFileDeleter final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScopedFileDeleter(const boost::filesystem::path& path) : _path {path} {}
|
||||||
|
~ScopedFileDeleter() { boost::filesystem::remove(_path); }
|
||||||
|
private:
|
||||||
|
boost::filesystem::path _path;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*static void check(bool cond)
|
||||||
|
{
|
||||||
|
if (!cond)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
#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()); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrack(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto track {Track::create(session, "MyTrackFile")};
|
||||||
|
CHECK(track);
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto track {Track::getById(session, trackId)};
|
||||||
|
CHECK(track);
|
||||||
|
CHECK(Track::getAll(session).size() == 1);
|
||||||
|
|
||||||
|
track.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleArtist(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType artistId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artist {Artist::create(session, "MyArtist")};
|
||||||
|
CHECK(artist);
|
||||||
|
session.flush();
|
||||||
|
artistId = artist.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
CHECK(Artist::getAll(session).size() == 1);
|
||||||
|
|
||||||
|
auto artists {Artist::getAllOrphans(session)};
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artist {Artist::getById(session, artistId)};
|
||||||
|
CHECK(artist);
|
||||||
|
artist.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleRelease(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType releaseId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto release {Release::create(session, "MyRelease")};
|
||||||
|
CHECK(release);
|
||||||
|
session.flush();
|
||||||
|
releaseId = release.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto releases {Release::getAllOrphans(session)};
|
||||||
|
CHECK(releases.size() == 1);
|
||||||
|
CHECK(releases.front().id() == releaseId);
|
||||||
|
|
||||||
|
releases = Release::getAll(session);
|
||||||
|
CHECK(releases.size() == 1);
|
||||||
|
CHECK(releases.front().id() == releaseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto release {Release::getById(session, releaseId)};
|
||||||
|
CHECK(release);
|
||||||
|
release.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleCluster(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType clusterTypeId {};
|
||||||
|
IdType clusterId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto clusterType {ClusterType::create(session, "MyType")};
|
||||||
|
CHECK(clusterType);
|
||||||
|
auto cluster {Cluster::create(session, clusterType, "MyCluster")};
|
||||||
|
CHECK(cluster);
|
||||||
|
session.flush();
|
||||||
|
clusterTypeId = clusterType.id();
|
||||||
|
clusterId = cluster.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto clusters {Cluster::getAll(session)};
|
||||||
|
CHECK(clusters.size() == 1);
|
||||||
|
CHECK(clusters.front().id() == clusterId);
|
||||||
|
CHECK(clusters.front()->getType().id() == clusterTypeId);
|
||||||
|
|
||||||
|
auto clusterTypes {ClusterType::getAll(session)};
|
||||||
|
CHECK(clusterTypes.size() == 1);
|
||||||
|
CHECK(clusterTypes.front().id() == clusterTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto cluster {Cluster::getById(session, clusterId)};
|
||||||
|
CHECK(cluster);
|
||||||
|
CHECK(cluster->getType().id() == clusterTypeId);
|
||||||
|
auto clusterType {ClusterType::getById(session, clusterTypeId)};
|
||||||
|
CHECK(clusterType);
|
||||||
|
|
||||||
|
cluster.remove();
|
||||||
|
clusterType.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleArtist(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType artistId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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)};
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
artistId = artist.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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(track->getArtistLinks().size() == 1);
|
||||||
|
auto artistLink {track->getArtistLinks().front()};
|
||||||
|
CHECK(artistLink->getTrack().id() == trackId);
|
||||||
|
CHECK(artistLink->getArtist().id() == artistId);
|
||||||
|
|
||||||
|
CHECK(track->getArtists(TrackArtistLink::Type::Artist).size() == 1);
|
||||||
|
CHECK(track->getArtists(TrackArtistLink::Type::ReleaseArtist).size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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(artist->getTracks(TrackArtistLink::Type::ReleaseArtist).size() == 0);
|
||||||
|
CHECK(artist->getTracks(TrackArtistLink::Type::Artist).size() == 1);
|
||||||
|
|
||||||
|
track.remove();
|
||||||
|
artist.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleArtistMultiRoles(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType artistId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
artistId = artist.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto track {Track::getById(session, trackId)};
|
||||||
|
CHECK(track);
|
||||||
|
|
||||||
|
auto artists {track->getArtists(TrackArtistLink::Type::Artist)};
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
|
||||||
|
artists = track->getArtists(TrackArtistLink::Type::ReleaseArtist);
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artist {Artist::getById(session, artistId)};
|
||||||
|
auto track {Track::getById(session, trackId)};
|
||||||
|
|
||||||
|
track.remove();
|
||||||
|
artist.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackMultiArtists(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType artist1Id {};
|
||||||
|
IdType artist2Id {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
artist1Id = artist1.id();
|
||||||
|
artist2Id = artist2.id();
|
||||||
|
|
||||||
|
CHECK(artist1Id != artist2Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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(track->getArtists(TrackArtistLink::Type::Artist).size() == 2);
|
||||||
|
CHECK(track->getArtists(TrackArtistLink::Type::ReleaseArtist).size() == 0);
|
||||||
|
CHECK(Artist::getAll(session).size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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(TrackArtistLink::Type::ReleaseArtist).size() == 0);
|
||||||
|
CHECK(artist1->getTracks(TrackArtistLink::Type::Artist).size() == 1);
|
||||||
|
CHECK(artist2->getTracks(TrackArtistLink::Type::ReleaseArtist).size() == 0);
|
||||||
|
CHECK(artist2->getTracks(TrackArtistLink::Type::Artist).size() == 1);
|
||||||
|
|
||||||
|
track.remove();
|
||||||
|
artist1.remove();
|
||||||
|
artist2.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleRelease(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType releaseId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto track {Track::create(session, "dummy")};
|
||||||
|
CHECK(track);
|
||||||
|
auto release {Release::create(session, "dummy")};
|
||||||
|
CHECK(release);
|
||||||
|
|
||||||
|
track.modify()->setRelease(release);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
releaseId = release.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto track {Track::getById(session, trackId)};
|
||||||
|
CHECK(track);
|
||||||
|
CHECK(track->getRelease());
|
||||||
|
CHECK(track->getRelease().id() == releaseId);
|
||||||
|
|
||||||
|
auto release {Release::getById(session, releaseId)};
|
||||||
|
CHECK(release);
|
||||||
|
CHECK(release->getTracks().size() == 1);
|
||||||
|
CHECK(release->getTracks().front().id() == trackId);
|
||||||
|
|
||||||
|
track.remove();
|
||||||
|
release.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleCluster(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType clusterId {};
|
||||||
|
IdType clusterTypeId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto track {Track::create(session, "dummy")};
|
||||||
|
auto clusterType {ClusterType::create(session, "MyType")};
|
||||||
|
auto cluster {Cluster::create(session, clusterType, "MyCluster")};
|
||||||
|
|
||||||
|
cluster.modify()->addTrack(track);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
clusterTypeId = clusterType.id();
|
||||||
|
clusterId = cluster.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto tracks {Track::getByFilter(session, {clusterId})};
|
||||||
|
CHECK(tracks.size() == 1);
|
||||||
|
CHECK(tracks.front().id() == trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto track {Track::getById(session, trackId)};
|
||||||
|
auto cluster {Cluster::getById(session, clusterId)};
|
||||||
|
auto clusterType {ClusterType::getById(session, clusterTypeId)};
|
||||||
|
|
||||||
|
track.remove();
|
||||||
|
cluster.remove();
|
||||||
|
clusterType.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleReleaseSingleCluster(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType releaseId {};
|
||||||
|
IdType clusterId {};
|
||||||
|
IdType clusterTypeId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
releaseId = release.id();
|
||||||
|
clusterTypeId = clusterType.id();
|
||||||
|
clusterId = cluster.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto releases {Release::getByFilter(session, {clusterId})};
|
||||||
|
CHECK(releases.size() == 1);
|
||||||
|
CHECK(releases.front().id() == releaseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType artistId {};
|
||||||
|
IdType cluster1Id {};
|
||||||
|
IdType cluster2Id {};
|
||||||
|
IdType clusterTypeId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
artistId = artist.id();
|
||||||
|
clusterTypeId = clusterType.id();
|
||||||
|
cluster1Id = cluster1.id();
|
||||||
|
cluster2Id = cluster2.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artists {Artist::getByFilter(session, {cluster1Id})};
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
|
||||||
|
artists = Artist::getByFilter(session, {cluster2Id});
|
||||||
|
CHECK(artists.size() == 0);
|
||||||
|
|
||||||
|
auto cluster2 {Cluster::getById(session, cluster2Id)};
|
||||||
|
auto track {Track::getById(session, trackId)};
|
||||||
|
cluster2.modify()->addTrack(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artists {Artist::getByFilter(session, {cluster1Id})};
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
|
||||||
|
artists = Artist::getByFilter(session, {cluster2Id});
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
|
||||||
|
artists = Artist::getByFilter(session, {cluster1Id, cluster2Id});
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleArtistMultiRolesMultiClusters(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType artistId {};
|
||||||
|
IdType clusterId {};
|
||||||
|
IdType clusterTypeId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
artistId = artist.id();
|
||||||
|
clusterTypeId = clusterType.id();
|
||||||
|
clusterId = cluster.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artists {Artist::getByFilter(session, {clusterId})};
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testMultiTracksSingleArtistMultiClusters(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
const std::size_t nbTracks {10};
|
||||||
|
const std::size_t nbClusters {5};
|
||||||
|
IdType artistId {};
|
||||||
|
IdType clusterTypeId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artist {Artist::create(session, "MyArtist")};
|
||||||
|
|
||||||
|
auto clusterType {ClusterType::create(session, "MyType")};
|
||||||
|
std::vector<Cluster::pointer> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
artistId = artist.id();
|
||||||
|
clusterTypeId = clusterType.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
std::vector<Cluster::pointer> clusters {Cluster::getAll(session)};
|
||||||
|
CHECK(clusters.size() == nbClusters);
|
||||||
|
|
||||||
|
std::set<IdType> clusterIds;
|
||||||
|
std::transform(std::cbegin(clusters), std::cend(clusters), std::inserter(clusterIds, std::begin(clusterIds)), [](const Cluster::pointer& cluster) { return cluster.id(); });
|
||||||
|
|
||||||
|
auto artists {Artist::getByFilter(session, clusterIds)};
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
std::vector<Cluster::pointer> clusters {Cluster::getAll(session)};
|
||||||
|
for (auto& cluster : clusters)
|
||||||
|
cluster.remove();
|
||||||
|
|
||||||
|
std::vector<Track::pointer> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleReleaseSingleArtist(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType releaseId {};
|
||||||
|
IdType artistId {};
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
releaseId = release.id();
|
||||||
|
artistId = artist.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artist {Artist::getById(session, artistId)};
|
||||||
|
CHECK(artist);
|
||||||
|
auto releases {artist->getReleases()};
|
||||||
|
CHECK(releases.size() == 1);
|
||||||
|
CHECK(releases.front().id() == releaseId);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleReleaseSingleArtistSingleCluster(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType releaseId {};
|
||||||
|
IdType artistId {};
|
||||||
|
IdType clusterId {};
|
||||||
|
IdType clusterTypeId {};
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
releaseId = release.id();
|
||||||
|
artistId = artist.id();
|
||||||
|
clusterId = cluster.id();
|
||||||
|
clusterTypeId = clusterType.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artists {Artist::getByFilter(session, {clusterId})};
|
||||||
|
CHECK(artists.size() == 1);
|
||||||
|
CHECK(artists.front().id() == artistId);
|
||||||
|
|
||||||
|
auto artist {Artist::getById(session, artistId)};
|
||||||
|
auto releases {artist->getReleases()};
|
||||||
|
CHECK(releases.size() == 1);
|
||||||
|
CHECK(releases.front().id() == releaseId);
|
||||||
|
|
||||||
|
releases = artist->getReleases({clusterId});
|
||||||
|
CHECK(releases.size() == 1);
|
||||||
|
CHECK(releases.front().id() == releaseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testSingleTrackSingleReleaseSingleArtistMultiClusters(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
IdType trackId {};
|
||||||
|
IdType releaseId {};
|
||||||
|
IdType artistId {};
|
||||||
|
IdType cluster1Id {};
|
||||||
|
IdType cluster2Id {};
|
||||||
|
IdType clusterTypeId {};
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
trackId = track.id();
|
||||||
|
releaseId = release.id();
|
||||||
|
artistId = artist.id();
|
||||||
|
cluster1Id = cluster1.id();
|
||||||
|
cluster2Id = cluster2.id();
|
||||||
|
clusterTypeId = clusterType.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
auto artist {Artist::getById(session, artistId)};
|
||||||
|
auto releases {artist->getReleases()};
|
||||||
|
CHECK(releases.size() == 1);
|
||||||
|
CHECK(releases.front().id() == releaseId);
|
||||||
|
|
||||||
|
releases = artist->getReleases({cluster1Id, cluster2Id});
|
||||||
|
CHECK(releases.size() == 1);
|
||||||
|
CHECK(releases.front().id() == releaseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testDatabaseEmpty(Wt::Dbo::Session& session)
|
||||||
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {session};
|
||||||
|
|
||||||
|
CHECK(Artist::getAll(session).size() == 0);
|
||||||
|
CHECK(Cluster::getAll(session).size() == 0);
|
||||||
|
CHECK(ClusterType::getAll(session).size() == 0);
|
||||||
|
CHECK(Release::getAll(session).size() == 0);
|
||||||
|
CHECK(Track::getAll(session).size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boost::filesystem::path tmpFile {boost::filesystem::temp_directory_path() / boost::filesystem::unique_path()};
|
||||||
|
ScopedFileDeleter tmpFileDeleter {tmpFile};
|
||||||
|
|
||||||
|
std::cout << "Database test file: '" << tmpFile.string() << "'" << std::endl;
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::Dbo::SqlConnectionPool> connectionPool{ Handler::createConnectionPool(tmpFile) };
|
||||||
|
|
||||||
|
Handler db {*connectionPool};
|
||||||
|
Wt::Dbo::Session& session {db.getSession()};
|
||||||
|
|
||||||
|
auto runTest = [&session](const std::string& name, std::function<void(Wt::Dbo::Session&)> testFunc)
|
||||||
|
{
|
||||||
|
std::cout << "Running test '" << name << "'..." << std::endl;
|
||||||
|
testFunc(session);
|
||||||
|
testDatabaseEmpty(session);
|
||||||
|
std::cout << "Running test '" << name << "': SUCCESS" << std::endl;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define RUN_TEST(test) runTest(#test, test)
|
||||||
|
|
||||||
|
RUN_TEST(testSingleTrack);
|
||||||
|
RUN_TEST(testSingleArtist);
|
||||||
|
RUN_TEST(testSingleRelease);
|
||||||
|
RUN_TEST(testSingleCluster);
|
||||||
|
|
||||||
|
RUN_TEST(testSingleTrackSingleArtist);
|
||||||
|
RUN_TEST(testSingleTrackSingleArtistMultiRoles);
|
||||||
|
RUN_TEST(testSingleTrackMultiArtists);
|
||||||
|
|
||||||
|
RUN_TEST(testSingleTrackSingleRelease);
|
||||||
|
|
||||||
|
RUN_TEST(testSingleTrackSingleCluster);
|
||||||
|
|
||||||
|
RUN_TEST(testSingleTrackSingleReleaseSingleCluster);
|
||||||
|
RUN_TEST(testSingleTrackSingleArtistMultiClusters);
|
||||||
|
RUN_TEST(testSingleTrackSingleArtistMultiRolesMultiClusters);
|
||||||
|
RUN_TEST(testMultiTracksSingleArtistMultiClusters);
|
||||||
|
|
||||||
|
RUN_TEST(testSingleTrackSingleReleaseSingleArtist);
|
||||||
|
|
||||||
|
RUN_TEST(testSingleTrackSingleReleaseSingleArtistSingleCluster);
|
||||||
|
RUN_TEST(testSingleTrackSingleReleaseSingleArtistMultiClusters);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Caught exception: " << e.what() << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
@@ -1,12 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <assert.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "DataNormalizer.hpp"
|
#include "DataNormalizer.hpp"
|
||||||
#include "Network.hpp"
|
#include "Network.hpp"
|
||||||
|
|
||||||
#define CHECK(pred) assert((pred))
|
|
||||||
|
|
||||||
using namespace SOM;
|
using namespace SOM;
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
Reference in New Issue
Block a user