diff --git a/approot/messages.xml b/approot/messages.xml
index cc8ba74c..1fcfeea3 100644
--- a/approot/messages.xml
+++ b/approot/messages.xml
@@ -190,12 +190,12 @@
Producers
- Album artists
- Album artist
+ Album artist
+ Album artists
- Remixers
- Remixer
+ Remixer
+ Remixers
@@ -213,6 +213,7 @@
Compilation
Demo
DJ-mix
+Field recording
Interview
Live
Mixtape/Street
diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml
index aca2df14..f396330e 100644
--- a/approot/messages_fr.xml
+++ b/approot/messages_fr.xml
@@ -204,21 +204,22 @@
Disque {1}
Type
Album
-Broadcast
+Diffusion
EP
Other
Single
-Audiobook
-Audio drama
+Livre audio
+Drame audio
Compilation
-Demo
+Démo
DJ-mix
+Enregistrement sur le terrain
Interview
Live
Mixtape/Street
Remix
-Soundtrack
-Spokenword
+Bande son
+Création parlée
Supprimer la liste de lecture ?
diff --git a/approot/messages_it.xml b/approot/messages_it.xml
index 7baa5358..69d30b39 100644
--- a/approot/messages_it.xml
+++ b/approot/messages_it.xml
@@ -220,6 +220,7 @@
+
diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml
index f3e20cea..60853a67 100644
--- a/approot/messages_zh.xml
+++ b/approot/messages_zh.xml
@@ -220,6 +220,7 @@
+
删除播放列表?
diff --git a/conf/lms.conf b/conf/lms.conf
index 2013d7c0..34583eee 100644
--- a/conf/lms.conf
+++ b/conf/lms.conf
@@ -84,7 +84,10 @@ cover-max-cache-size = 30;
cover-jpeg-quality = 75;
# Preferred file names for covers (order is important)
-cover-preferred-file-names = ("cover", "front" );
+cover-preferred-file-names = ("cover", "front");
+
+# File names for artist images (order is important)
+artist-image-file-names = ("artist");
# Playqueue max entry count
playqueue-max-entry-count = 1000;
diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp
index 47d062a1..9fca99a5 100644
--- a/src/libs/database/impl/Artist.cpp
+++ b/src/libs/database/impl/Artist.cpp
@@ -180,7 +180,7 @@ namespace Database
return session.getDboSession().query("SELECT COUNT(*) FROM artist");
}
- std::vector Artist::find(Session& session, const std::string& name)
+ std::vector Artist::find(Session& session, std::string_view name)
{
session.checkReadTransaction();
diff --git a/src/libs/database/impl/IdTypeTraits.hpp b/src/libs/database/impl/IdTypeTraits.hpp
index bdfa0045..42dd2b2e 100644
--- a/src/libs/database/impl/IdTypeTraits.hpp
+++ b/src/libs/database/impl/IdTypeTraits.hpp
@@ -26,35 +26,34 @@
namespace Wt::Dbo
{
+ template
+ struct sql_value_traits::value>::type>
+ {
+ static_assert(!std::is_same_v, "Cannot use IdType, use derived types");
+ static const bool specialized = true;
- template
- struct sql_value_traits::value>::type>
- {
- static_assert(!std::is_same_v, "Cannot use IdType, use derived types");
- static const bool specialized = true;
+ static std::string type(SqlConnection* conn, int size)
+ {
+ return sql_value_traits::type(conn, size);
+ }
- static std::string type(SqlConnection *conn, int size)
- {
- return sql_value_traits::type(conn, size);
- }
+ static void bind(const T& v, SqlStatement* statement, int column, int size)
+ {
+ sql_value_traits::bind(v.getValue(), statement, column, size);
+ }
- static void bind(const T& v, SqlStatement *statement, int column, int size)
- {
- sql_value_traits::bind(v.getValue(), statement, column, size);
- }
+ static bool read(T& v, SqlStatement* statement, int column, int size)
+ {
+ typename T::ValueType value;
+ if (sql_value_traits::read(value, statement, column, size))
+ {
+ v = value;
+ return true;
+ }
- static bool read(T& v, SqlStatement *statement, int column, int size)
- {
- typename T::ValueType value;
- if (sql_value_traits::read(value, statement, column, size))
- {
- v = value;
- return true;
- }
-
- v = {};
- return false;
- }
- };
+ v = {};
+ return false;
+ }
+ };
}
diff --git a/src/libs/database/impl/Listen.cpp b/src/libs/database/impl/Listen.cpp
index 57005412..87391546 100644
--- a/src/libs/database/impl/Listen.cpp
+++ b/src/libs/database/impl/Listen.cpp
@@ -25,112 +25,113 @@
#include "SqlQuery.hpp"
#include "Utils.hpp"
-namespace
-{
- using namespace Database;
-
- Wt::Dbo::Query createArtistsQuery(Wt::Dbo::Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType)
- {
- auto query{ session.query("SELECT a.id from artist a")
- .join("track t ON t.id = t_a_l.track_id")
- .join("track_artist_link t_a_l ON t_a_l.artist_id = a.id")
- .join("listen l ON l.track_id = t.id")
- .where("l.user_id = ?").bind(userId)
- .where("l.backend = ?").bind(backend) };
-
- if (linkType)
- query.where("t_a_l.type = ?").bind(*linkType);
-
- if (!clusterIds.empty())
- {
- std::ostringstream oss;
- oss << "a.id IN (SELECT DISTINCT a.id 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 cluster c ON c.id = t_c.cluster_id"
- " INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
-
- WhereClause clusterClause;
- for (auto id : clusterIds)
- {
- clusterClause.Or(WhereClause("c.id = ?"));
- query.bind(id);
- }
-
- oss << " " << clusterClause.get();
- oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
-
- query.where(oss.str());
- }
-
- return query;
- }
-
- Wt::Dbo::Query createReleasesQuery(Wt::Dbo::Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds)
- {
- auto query{ session.query("SELECT r.id from release r")
- .join("track t ON t.release_id = r.id")
- .join("listen l ON l.track_id = t.id")
- .where("l.user_id = ?").bind(userId)
- .where("l.backend = ?").bind(backend) };
-
- if (!clusterIds.empty())
- {
- std::ostringstream oss;
- oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
- " INNER JOIN track t ON t.release_id = r.id"
- " INNER JOIN cluster c ON c.id = t_c.cluster_id"
- " INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
-
- WhereClause clusterClause;
- for (ClusterId id : clusterIds)
- {
- clusterClause.Or(WhereClause("c.id = ?"));
- query.bind(id);
- }
-
- oss << " " << clusterClause.get();
- oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
-
- query.where(oss.str());
- }
-
- return query;
- }
-
- Wt::Dbo::Query createTracksQuery(Wt::Dbo::Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds)
- {
- auto query{ session.query("SELECT t.id from track t")
- .join("listen l ON l.track_id = t.id")
- .where("l.user_id = ?").bind(userId)
- .where("l.backend = ?").bind(backend) };
-
- if (!clusterIds.empty())
- {
- std::ostringstream oss;
- oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
- " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
- " INNER JOIN cluster c ON c.id = t_c.cluster_id";
-
- WhereClause clusterClause;
- for (auto id : clusterIds)
- {
- clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
- query.bind(id);
- }
-
- oss << " " << clusterClause.get();
- oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")";
-
- query.where(oss.str());
- }
-
- return query;
- }
-}
-
namespace Database
{
+ namespace
+ {
+ Wt::Dbo::Query createArtistsQuery(Wt::Dbo::Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds, std::optional linkType)
+ {
+ auto query{ session.query("SELECT a.id from artist a")
+ .join("track t ON t.id = t_a_l.track_id")
+ .join("track_artist_link t_a_l ON t_a_l.artist_id = a.id")
+ .join("listen l ON l.track_id = t.id")
+ .where("l.user_id = ?").bind(userId)
+ .where("l.backend = ?").bind(backend) };
+
+ if (linkType)
+ query.where("t_a_l.type = ?").bind(*linkType);
+
+ if (!clusterIds.empty())
+ {
+ std::ostringstream oss;
+ oss << "a.id IN (SELECT DISTINCT a.id 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 cluster c ON c.id = t_c.cluster_id"
+ " INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
+
+ WhereClause clusterClause;
+ for (auto id : clusterIds)
+ {
+ clusterClause.Or(WhereClause("c.id = ?"));
+ query.bind(id);
+ }
+
+ oss << " " << clusterClause.get();
+ oss << " GROUP BY t.id,a.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
+
+ query.where(oss.str());
+ }
+
+ return query;
+ }
+
+ Wt::Dbo::Query createReleasesQuery(Wt::Dbo::Session& session, UserId userId, ScrobblingBackend backend, const std::vector& clusterIds)
+ {
+ auto query{ session.query("SELECT r.id from release r")
+ .join("track t ON t.release_id = r.id")
+ .join("listen l ON l.track_id = t.id")
+ .where("l.user_id = ?").bind(userId)
+ .where("l.backend = ?").bind(backend) };
+
+ if (!clusterIds.empty())
+ {
+ std::ostringstream oss;
+ oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
+ " INNER JOIN track t ON t.release_id = r.id"
+ " INNER JOIN cluster c ON c.id = t_c.cluster_id"
+ " INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
+
+ WhereClause clusterClause;
+ for (ClusterId id : clusterIds)
+ {
+ clusterClause.Or(WhereClause("c.id = ?"));
+ query.bind(id);
+ }
+
+ oss << " " << clusterClause.get();
+ oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size() << ")";
+
+ query.where(oss.str());
+ }
+
+ return query;
+ }
+
+ Wt::Dbo::Query createTracksQuery(Wt::Dbo::Session& session, UserId userId, ArtistId artistId, ScrobblingBackend backend, const std::vector& clusterIds)
+ {
+ auto query{ session.query("SELECT t.id from track t")
+ .join("listen l ON l.track_id = t.id")
+ .where("l.user_id = ?").bind(userId)
+ .where("l.backend = ?").bind(backend) };
+
+ if (artistId.isValid())
+ query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id").where("t_a_l.artist_id = ?").bind(artistId);
+
+ if (!clusterIds.empty())
+ {
+ std::ostringstream oss;
+ oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
+ " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
+ " INNER JOIN cluster c ON c.id = t_c.cluster_id";
+
+ WhereClause clusterClause;
+ for (auto id : clusterIds)
+ {
+ clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
+ query.bind(id);
+ }
+
+ oss << " " << clusterClause.get();
+ oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size() << ")";
+
+ query.where(oss.str());
+ }
+
+ return query;
+ }
+ }
+
Listen::Listen(ObjectPtr user, ObjectPtr