diff --git a/INSTALL.md b/INSTALL.md
index b93a9e1c..4be9decd 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -106,7 +106,7 @@ You can define which authentication backend to be used thanks to the `authentica
* `PAM`: the user/password authentication request is forwarded to PAM (see the default [PAM configuration file](conf/pam/lms) provided).
* `http-headers`: _LMS_ uses a configurable HTTP header field, typically set by a reverse proxy to handle [SSO](https://en.wikipedia.org/wiki/Single_sign-on), to extract the login name. You can customize the field to be used using the `http-headers-login-field` option.
__Note__: the first created user is the admin user
-### `internal` backend: reset admin password
+#### `internal` backend: reset admin password
Open the the database file located in `/var/lms/lms.db` using `sqlite3`:
```sh
sqlite3 /var/lms/lms.db
diff --git a/README.md b/README.md
index 2c1302a5..c90c7199 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,9 @@ A [demo instance](http://lms-demo.poupon.dev) is available. Note the administrat
* Audio transcode for maximum interoperability and low bandwith requirements
* Multi-value tags: artists, genres, composers, lyricists, moods, ...
* [MusicBrainz Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) support to handle duplicated artist and release names
-* [ListenBrainz](https://listenbrainz.org) support for scrobbling and synchronizing listens
+* [ListenBrainz](https://listenbrainz.org) support for:
+ * Scrobbling and synchronizing listens
+ * Synchronizing 'love' feedbacks
* Compilation support
* Disc subtitles support
* ReplayGain support
@@ -50,7 +52,7 @@ __Notes on the self-organizing map__:
## Subsonic API
The API version implemented is 1.16.0 and has been tested on _Android_ using _Subsonic Player_, _Ultrasonic_ and _DSub_.
-Since _LMS_ uses metadata tags to organize music, a compatibility mode is used to navigate through the collection when using the directory browsing commands.
+Since _LMS_ uses metadata tags to organize music, a compatibility mode is used to browse the collection when using the directory browsing commands.
The Subsonic API is enabled by default.
@@ -79,9 +81,9 @@ $setmulti(albumartistssort,%_albumartists_sort%)
## Security considerations
_Wt_ (the web framework used) has some [built-in security measures](https://www.webtoolkit.eu/wt/features#security), but _LMS_ also has some too:
-* to mitigate brute force login attempts, _LMS_ uses an internal login throttler based on the client IP address. The `Client-IP` or `X-Forwarded-For` headers are used to determined the real IP adress, so make sure to properly configure your reverse proxy to filter or even erase the values (see example in [INSTALL.md](INSTALL.md)).
+* to mitigate brute force login attempts, _LMS_ uses an internal login throttler based on the client IP address. The `Client-IP` or `X-Forwarded-For` headers are used to determine the real IP adress, so make sure to properly configure your reverse proxy to filter or even erase the values (see example in [INSTALL.md](INSTALL.md)).
* all passwords are stored hashed and salted using [bcrypt](https://fr.wikipedia.org/wiki/Bcrypt)
-* all the resources relative to the music collection (tracks, covers, etc.) are private to a session
+* all the resources relative to the music collection (tracks, covers, etc.) are private to an anthenticated session
## Installation
diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml
index 7a9c708a..e1fff9b1 100644
--- a/approot/messages_fr.xml
+++ b/approot/messages_fr.xml
@@ -137,7 +137,7 @@
Artistes divers
-Apparitions
+ApparaƮt dans
Artistes similaires
diff --git a/conf/lms.conf b/conf/lms.conf
index 7817c217..ca7dae59 100644
--- a/conf/lms.conf
+++ b/conf/lms.conf
@@ -36,10 +36,14 @@ http-server-thread-count = 0;
# ListenBrainz root API
listenbrainz-api-base-url = "https://api.listenbrainz.org";
-# How many listens to retrieve when syncing (0 disables sync)
+# How many listens to retrieve when syncing (0 to disable sync)
listenbrainz-max-sync-listen-count = 1000;
-# How often to resync listens (0 disables sync)
+# How often to resync listens (0 to disable sync)
listenbrainz-sync-listens-period-hours = 1;
+# How many feedbacks to retrieve when syncing (0 to disables sync)
+listenbrainz-max-sync-feedback-count = 1000;
+# How often to resync feedbacks (0 to disable sync)
+listenbrainz-sync-feedbacks-period-hours = 1;
# Acousticbrainz root API
acousticbrainz-api-base-url = "https://acousticbrainz.org";
diff --git a/src/libs/services/database/impl/Artist.cpp b/src/libs/services/database/impl/Artist.cpp
index 1243d043..7d904bd8 100644
--- a/src/libs/services/database/impl/Artist.cpp
+++ b/src/libs/services/database/impl/Artist.cpp
@@ -136,7 +136,8 @@ createQuery(Session& session, const Artist::FindParameters& params)
assert(params.scrobbler);
query.join("starred_artist s_a ON s_a.artist_id = a.id")
.where("s_a.user_id = ?").bind(params.starringUser)
- .where("s_a.scrobbler = ?").bind(*params.scrobbler);
+ .where("s_a.scrobbler = ?").bind(*params.scrobbler)
+ .where("s_a.scrobbling_state <> ?").bind(ScrobblingState::PendingRemove);
}
if (!params.clusters.empty())
diff --git a/src/libs/services/database/impl/Migration.cpp b/src/libs/services/database/impl/Migration.cpp
index fed5d7fc..a2c9616c 100644
--- a/src/libs/services/database/impl/Migration.cpp
+++ b/src/libs/services/database/impl/Migration.cpp
@@ -571,6 +571,17 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
session.getDboSession().execute("ALTER TABLE track_artist_link_backup RENAME TO track_artist_link");
}
+ static
+ void
+ migrateFromV34(Session& session)
+ {
+ // Add scrobbling state
+ // By default, everythin needs to be sent
+ session.getDboSession().execute("ALTER TABLE starred_artist ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/0)) + ")");
+ session.getDboSession().execute("ALTER TABLE starred_release ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/0)) + ")");
+ session.getDboSession().execute("ALTER TABLE starred_track ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/0)) + ")");
+ }
+
void
doDbMigration(Session& session)
{
@@ -611,6 +622,7 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
{31, migrateFromV31},
{32, migrateFromV32},
{33, migrateFromV33},
+ {34, migrateFromV34},
};
while (1)
diff --git a/src/libs/services/database/impl/Migration.hpp b/src/libs/services/database/impl/Migration.hpp
index 108c8c6c..65b8b284 100644
--- a/src/libs/services/database/impl/Migration.hpp
+++ b/src/libs/services/database/impl/Migration.hpp
@@ -26,7 +26,7 @@ namespace Database
class Session;
using Version = std::size_t;
- static constexpr Version LMS_DATABASE_VERSION {34};
+ static constexpr Version LMS_DATABASE_VERSION {35};
class VersionInfo
{
public:
diff --git a/src/libs/services/database/impl/Release.cpp b/src/libs/services/database/impl/Release.cpp
index 0d18bf03..3ca55456 100644
--- a/src/libs/services/database/impl/Release.cpp
+++ b/src/libs/services/database/impl/Release.cpp
@@ -64,7 +64,8 @@ createQuery(Session& session, const Release::FindParameters& params)
assert(params.scrobbler);
query.join("starred_release s_r ON s_r.release_id = r.id")
.where("s_r.user_id = ?").bind(params.starringUser)
- .where("s_r.scrobbler = ?").bind(*params.scrobbler);
+ .where("s_r.scrobbler = ?").bind(*params.scrobbler)
+ .where("s_r.scrobbling_state <> ?").bind(ScrobblingState::PendingRemove);
}
if (params.artist.isValid())
diff --git a/src/libs/services/database/impl/StarredArtist.cpp b/src/libs/services/database/impl/StarredArtist.cpp
index ed617665..1960a66e 100644
--- a/src/libs/services/database/impl/StarredArtist.cpp
+++ b/src/libs/services/database/impl/StarredArtist.cpp
@@ -22,6 +22,7 @@
#include
#include "services/database/Artist.hpp"
+#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
diff --git a/src/libs/services/database/impl/StarredRelease.cpp b/src/libs/services/database/impl/StarredRelease.cpp
index c72331d5..22b556bf 100644
--- a/src/libs/services/database/impl/StarredRelease.cpp
+++ b/src/libs/services/database/impl/StarredRelease.cpp
@@ -22,6 +22,7 @@
#include
#include "services/database/Release.hpp"
+#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
diff --git a/src/libs/services/database/impl/StarredTrack.cpp b/src/libs/services/database/impl/StarredTrack.cpp
index 6543654f..b2ddb486 100644
--- a/src/libs/services/database/impl/StarredTrack.cpp
+++ b/src/libs/services/database/impl/StarredTrack.cpp
@@ -22,6 +22,7 @@
#include
#include "services/database/Track.hpp"
+#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
@@ -60,6 +61,23 @@ namespace Database
.resultValue();
}
+ RangeResults
+ StarredTrack::find(Session& session, const FindParameters& params)
+ {
+ session.checkSharedLocked();
+
+ auto query {session.getDboSession().query("SELECT DISTINCT s_t.id FROM starred_track s_t")};
+
+ if (params.scrobbler)
+ query.where("s_t.scrobbler = ?").bind(*params.scrobbler);
+ if (params.scrobblingState)
+ query.where("s_t.scrobbling_state = ?").bind(*params.scrobblingState);
+ if (params.user.isValid())
+ query.where("s_t.user_id = ?").bind(params.user);
+
+ return execQuery(query, params.range);
+ }
+
StarredTrack::pointer
StarredTrack::create(Session& session, ObjectPtr