Subsonic API: reported correct starred date/time

This commit is contained in:
emeric
2023-10-02 22:21:22 +02:00
parent 7c7fbba319
commit 2732c85503
13 changed files with 524 additions and 521 deletions
@@ -30,33 +30,33 @@
namespace Database namespace Database
{ {
class Session; class Session;
class TrackList; class TrackList;
class User; class User;
} }
namespace Scrobbling namespace Scrobbling
{ {
class IScrobbler class IScrobbler
{ {
public: public:
virtual ~IScrobbler() = default; virtual ~IScrobbler() = default;
// Listens // Listens
virtual void listenStarted(const Listen& listen) = 0; virtual void listenStarted(const Listen& listen) = 0;
virtual void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) = 0; virtual void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) = 0;
virtual void addTimedListen(const TimedListen& listen) = 0; virtual void addTimedListen(const TimedListen& listen) = 0;
// Feedbacks // Feedbacks
virtual void onStarred(Database::StarredArtistId) = 0; virtual void onStarred(Database::StarredArtistId) = 0;
virtual void onUnstarred(Database::StarredArtistId) = 0; virtual void onUnstarred(Database::StarredArtistId) = 0;
virtual void onStarred(Database::StarredReleaseId) = 0; virtual void onStarred(Database::StarredReleaseId) = 0;
virtual void onUnstarred(Database::StarredReleaseId) = 0; virtual void onUnstarred(Database::StarredReleaseId) = 0;
virtual void onStarred(Database::StarredTrackId) = 0; virtual void onStarred(Database::StarredTrackId) = 0;
virtual void onUnstarred(Database::StarredTrackId) = 0; virtual void onUnstarred(Database::StarredTrackId) = 0;
}; };
std::unique_ptr<IScrobbler> createScrobbler(std::string_view backendName); std::unique_ptr<IScrobbler> createScrobbler(std::string_view backendName);
} // ns Scrobbling } // ns Scrobbling
@@ -37,272 +37,264 @@
namespace Scrobbling namespace Scrobbling
{ {
using namespace Database; using namespace Database;
std::unique_ptr<IScrobblingService> std::unique_ptr<IScrobblingService> createScrobblingService(boost::asio::io_context& ioContext, Db& db)
createScrobblingService(boost::asio::io_context& ioContext, Db& db) {
{ return std::make_unique<ScrobblingService>(ioContext, db);
return std::make_unique<ScrobblingService>(ioContext, db); }
}
ScrobblingService::ScrobblingService(boost::asio::io_context& ioContext, Db& db) ScrobblingService::ScrobblingService(boost::asio::io_context& ioContext, Db& db)
: _db {db} : _db{ db }
{ {
LMS_LOG(SCROBBLING, INFO) << "Starting service..."; LMS_LOG(SCROBBLING, INFO) << "Starting service...";
_scrobblers.emplace(Scrobbler::Internal, std::make_unique<InternalScrobbler>(_db)); _scrobblers.emplace(Scrobbler::Internal, std::make_unique<InternalScrobbler>(_db));
_scrobblers.emplace(Scrobbler::ListenBrainz, std::make_unique<ListenBrainz::Scrobbler>(ioContext, _db)); _scrobblers.emplace(Scrobbler::ListenBrainz, std::make_unique<ListenBrainz::Scrobbler>(ioContext, _db));
LMS_LOG(SCROBBLING, INFO) << "Service started!"; LMS_LOG(SCROBBLING, INFO) << "Service started!";
} }
ScrobblingService::~ScrobblingService() ScrobblingService::~ScrobblingService()
{ {
LMS_LOG(SCROBBLING, INFO) << "Service stopped!"; LMS_LOG(SCROBBLING, INFO) << "Service stopped!";
} }
void void ScrobblingService::listenStarted(const Listen& listen)
ScrobblingService::listenStarted(const Listen& listen) {
{ if (std::optional<Scrobbler> scrobbler{ getUserScrobbler(listen.userId) })
if (std::optional<Scrobbler> scrobbler {getUserScrobbler(listen.userId)}) _scrobblers[*scrobbler]->listenStarted(listen);
_scrobblers[*scrobbler]->listenStarted(listen); }
}
void void ScrobblingService::listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration)
ScrobblingService::listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) {
{ if (std::optional<Scrobbler> scrobbler{ getUserScrobbler(listen.userId) })
if (std::optional<Scrobbler> scrobbler {getUserScrobbler(listen.userId)}) _scrobblers[*scrobbler]->listenFinished(listen, duration);
_scrobblers[*scrobbler]->listenFinished(listen, duration); }
}
void void ScrobblingService::addTimedListen(const TimedListen& listen)
ScrobblingService::addTimedListen(const TimedListen& listen) {
{ if (std::optional<Scrobbler> scrobbler{ getUserScrobbler(listen.userId) })
if (std::optional<Scrobbler> scrobbler {getUserScrobbler(listen.userId)}) _scrobblers[*scrobbler]->addTimedListen(listen);
_scrobblers[*scrobbler]->addTimedListen(listen); }
}
std::optional<Scrobbler> std::optional<Scrobbler> ScrobblingService::getUserScrobbler(UserId userId)
ScrobblingService::getUserScrobbler(UserId userId) {
{ std::optional<Scrobbler> scrobbler;
std::optional<Scrobbler> scrobbler;
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
if (const User::pointer user {User::find(session, userId)}) if (const User::pointer user{ User::find(session, userId) })
scrobbler = user->getScrobbler(); scrobbler = user->getScrobbler();
return scrobbler; return scrobbler;
} }
ScrobblingService::ArtistContainer ScrobblingService::ArtistContainer ScrobblingService::getRecentArtists(UserId userId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range)
ScrobblingService::getRecentArtists(UserId userId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range) {
{ ArtistContainer res;
ArtistContainer res;
auto scrobbler {getUserScrobbler(userId)}; auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler) if (!scrobbler)
return res; return res;
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
res = Database::Listen::getRecentArtists(session, userId, *scrobbler, clusterIds, linkType, range); res = Database::Listen::getRecentArtists(session, userId, *scrobbler, clusterIds, linkType, range);
return res; return res;
} }
ScrobblingService::ReleaseContainer ScrobblingService::ReleaseContainer ScrobblingService::getRecentReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
ScrobblingService::getRecentReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range) {
{ ReleaseContainer res;
ReleaseContainer res;
auto scrobbler {getUserScrobbler(userId)}; auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler) if (!scrobbler)
return res; return res;
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
res = Database::Listen::getRecentReleases(session, userId, *scrobbler, clusterIds, range); res = Database::Listen::getRecentReleases(session, userId, *scrobbler, clusterIds, range);
return res; return res;
} }
ScrobblingService::TrackContainer ScrobblingService::TrackContainer ScrobblingService::getRecentTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
ScrobblingService::getRecentTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range) {
{ TrackContainer res;
TrackContainer res;
auto scrobbler {getUserScrobbler(userId)}; auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler) if (!scrobbler)
return res; return res;
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
res = Database::Listen::getRecentTracks(session, userId, *scrobbler, clusterIds, range); res = Database::Listen::getRecentTracks(session, userId, *scrobbler, clusterIds, range);
return res; return res;
} }
// Top // Top
ScrobblingService::ArtistContainer ScrobblingService::ArtistContainer ScrobblingService::getTopArtists(UserId userId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range)
ScrobblingService::getTopArtists(UserId userId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range) {
{ ArtistContainer res;
ArtistContainer res;
auto scrobbler {getUserScrobbler(userId)}; auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler) if (!scrobbler)
return res; return res;
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
res = Database::Listen::getTopArtists(session, userId, *scrobbler, clusterIds, linkType, range); res = Database::Listen::getTopArtists(session, userId, *scrobbler, clusterIds, linkType, range);
return res; return res;
} }
ScrobblingService::ReleaseContainer ScrobblingService::ReleaseContainer ScrobblingService::getTopReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
ScrobblingService::getTopReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range) {
{ ReleaseContainer res;
ReleaseContainer res;
auto scrobbler {getUserScrobbler(userId)}; auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler) if (!scrobbler)
return res; return res;
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
res = Database::Listen::getTopReleases(session, userId, *scrobbler, clusterIds, range); res = Database::Listen::getTopReleases(session, userId, *scrobbler, clusterIds, range);
return res; return res;
} }
ScrobblingService::TrackContainer ScrobblingService::TrackContainer ScrobblingService::getTopTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
ScrobblingService::getTopTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range) {
{ TrackContainer res;
TrackContainer res;
auto scrobbler {getUserScrobbler(userId)}; auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler) if (!scrobbler)
return res; return res;
Session& session {_db.getTLSSession()}; Session& session{ _db.getTLSSession() };
auto transaction {session.createSharedTransaction()}; auto transaction{ session.createSharedTransaction() };
res = Database::Listen::getTopTracks(session, userId, *scrobbler, clusterIds, range); res = Database::Listen::getTopTracks(session, userId, *scrobbler, clusterIds, range);
return res; return res;
} }
void void ScrobblingService::star(UserId userId, ArtistId artistId)
ScrobblingService::star(UserId userId, ArtistId artistId) {
{ star<Artist, ArtistId, StarredArtist>(userId, artistId);
star<Artist, ArtistId, StarredArtist>(userId, artistId); }
}
void void ScrobblingService::unstar(UserId userId, ArtistId artistId)
ScrobblingService::unstar(UserId userId, ArtistId artistId) {
{ unstar<Artist, ArtistId, StarredArtist>(userId, artistId);
unstar<Artist, ArtistId, StarredArtist>(userId, artistId); }
}
bool bool ScrobblingService::isStarred(UserId userId, ArtistId artistId)
ScrobblingService::isStarred(UserId userId, ArtistId artistId) {
{ return isStarred<Artist, ArtistId, StarredArtist>(userId, artistId);
return isStarred<Artist, ArtistId, StarredArtist>(userId, artistId); }
}
ScrobblingService::ArtistContainer Wt::WDateTime ScrobblingService::getStarredDateTime(UserId userId, ArtistId artistId)
ScrobblingService::getStarredArtists(UserId userId, const std::vector<ClusterId>& clusterIds, {
std::optional<TrackArtistLinkType> linkType, return getStarredDateTime<Artist, ArtistId, StarredArtist>(userId, artistId);
ArtistSortMethod sortMethod, }
Range range)
{
auto scrobbler {getUserScrobbler(userId)};
if (!scrobbler)
return {};
Artist::FindParameters params; ScrobblingService::ArtistContainer ScrobblingService::getStarredArtists(UserId userId, const std::vector<ClusterId>& clusterIds,
params.setStarringUser(userId, *scrobbler); std::optional<TrackArtistLinkType> linkType,
params.setClusters(clusterIds); ArtistSortMethod sortMethod,
params.setLinkType(linkType); Range range)
params.setSortMethod(sortMethod); {
params.setRange(range); auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler)
return {};
Session& session {_db.getTLSSession()}; Artist::FindParameters params;
auto transaction {session.createSharedTransaction()}; params.setStarringUser(userId, *scrobbler);
params.setClusters(clusterIds);
params.setLinkType(linkType);
params.setSortMethod(sortMethod);
params.setRange(range);
return Artist::find(session, params); Session& session{ _db.getTLSSession() };
} auto transaction{ session.createSharedTransaction() };
void return Artist::find(session, params);
ScrobblingService::star(UserId userId, ReleaseId releaseId) }
{
star<Release, ReleaseId, StarredRelease>(userId, releaseId);
}
void void ScrobblingService::star(UserId userId, ReleaseId releaseId)
ScrobblingService::unstar(UserId userId, ReleaseId releaseId) {
{ star<Release, ReleaseId, StarredRelease>(userId, releaseId);
unstar<Release, ReleaseId, StarredRelease>(userId, releaseId); }
}
bool void ScrobblingService::unstar(UserId userId, ReleaseId releaseId)
ScrobblingService::isStarred(UserId userId, ReleaseId releaseId) {
{ unstar<Release, ReleaseId, StarredRelease>(userId, releaseId);
return isStarred<Release, ReleaseId, StarredRelease>(userId, releaseId); }
}
ScrobblingService::ReleaseContainer bool ScrobblingService::isStarred(UserId userId, ReleaseId releaseId)
ScrobblingService::getStarredReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range) {
{ return isStarred<Release, ReleaseId, StarredRelease>(userId, releaseId);
auto scrobbler {getUserScrobbler(userId)}; }
if (!scrobbler)
return {};
Release::FindParameters params; Wt::WDateTime ScrobblingService::getStarredDateTime(UserId userId, ReleaseId releaseId)
params.setStarringUser(userId, *scrobbler); {
params.setClusters(clusterIds); return getStarredDateTime<Release, ReleaseId, StarredRelease>(userId, releaseId);
params.setSortMethod(ReleaseSortMethod::StarredDateDesc); }
params.setRange(range);
Session& session {_db.getTLSSession()}; ScrobblingService::ReleaseContainer ScrobblingService::getStarredReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
auto transaction {session.createSharedTransaction()}; {
auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler)
return {};
return Release::find(session, params); Release::FindParameters params;
} params.setStarringUser(userId, *scrobbler);
params.setClusters(clusterIds);
params.setSortMethod(ReleaseSortMethod::StarredDateDesc);
params.setRange(range);
void Session& session{ _db.getTLSSession() };
ScrobblingService::star(UserId userId, TrackId trackId) auto transaction{ session.createSharedTransaction() };
{
star<Track, TrackId, StarredTrack>(userId, trackId);
}
void return Release::find(session, params);
ScrobblingService::unstar(UserId userId, TrackId trackId) }
{
unstar<Track, TrackId, StarredTrack>(userId, trackId);
}
bool void ScrobblingService::star(UserId userId, TrackId trackId)
ScrobblingService::isStarred(UserId userId, TrackId trackId) {
{ star<Track, TrackId, StarredTrack>(userId, trackId);
return isStarred<Track, TrackId, StarredTrack>(userId, trackId); }
}
ScrobblingService::TrackContainer void ScrobblingService::unstar(UserId userId, TrackId trackId)
ScrobblingService::getStarredTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range) {
{ unstar<Track, TrackId, StarredTrack>(userId, trackId);
auto scrobbler {getUserScrobbler(userId)}; }
if (!scrobbler)
return {};
Track::FindParameters params; bool ScrobblingService::isStarred(UserId userId, TrackId trackId)
params.setStarringUser(userId, *scrobbler); {
params.setClusters(clusterIds); return isStarred<Track, TrackId, StarredTrack>(userId, trackId);
params.setSortMethod(TrackSortMethod::StarredDateDesc); }
params.setRange(range);
Session& session {_db.getTLSSession()}; Wt::WDateTime ScrobblingService::getStarredDateTime(UserId userId, TrackId trackId)
auto transaction {session.createSharedTransaction()}; {
return getStarredDateTime<Track, TrackId, StarredTrack>(userId, trackId);
}
return Track::find(session, params); ScrobblingService::TrackContainer ScrobblingService::getStarredTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
} {
auto scrobbler{ getUserScrobbler(userId) };
if (!scrobbler)
return {};
Track::FindParameters params;
params.setStarringUser(userId, *scrobbler);
params.setClusters(clusterIds);
params.setSortMethod(TrackSortMethod::StarredDateDesc);
params.setRange(range);
Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
return Track::find(session, params);
}
} // ns Scrobbling } // ns Scrobbling
@@ -28,74 +28,79 @@
namespace Scrobbling namespace Scrobbling
{ {
class ScrobblingService : public IScrobblingService class ScrobblingService : public IScrobblingService
{ {
public: public:
ScrobblingService(boost::asio::io_context& ioContext, Database::Db& db); ScrobblingService(boost::asio::io_context& ioContext, Database::Db& db);
~ScrobblingService(); ~ScrobblingService();
private: private:
void listenStarted(const Listen& listen) override; void listenStarted(const Listen& listen) override;
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override; void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
void addTimedListen(const TimedListen& listen) override; void addTimedListen(const TimedListen& listen) override;
ArtistContainer getRecentArtists(Database::UserId userId, ArtistContainer getRecentArtists(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType, std::optional<Database::TrackArtistLinkType> linkType,
Database::Range range) override; Database::Range range) override;
ReleaseContainer getRecentReleases(Database::UserId userId, ReleaseContainer getRecentReleases(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
Database::Range range) override; Database::Range range) override;
TrackContainer getRecentTracks(Database::UserId userId, TrackContainer getRecentTracks(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
Database::Range range) override; Database::Range range) override;
ArtistContainer getTopArtists(Database::UserId userId, ArtistContainer getTopArtists(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType, std::optional<Database::TrackArtistLinkType> linkType,
Database::Range range) override; Database::Range range) override;
ReleaseContainer getTopReleases(Database::UserId userId, ReleaseContainer getTopReleases(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
Database::Range range) override; Database::Range range) override;
TrackContainer getTopTracks(Database::UserId userId, TrackContainer getTopTracks(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
Database::Range range) override; Database::Range range) override;
void star(Database::UserId userId, Database::ArtistId artistId) override; void star(Database::UserId userId, Database::ArtistId artistId) override;
void unstar(Database::UserId userId, Database::ArtistId artistId) override; void unstar(Database::UserId userId, Database::ArtistId artistId) override;
bool isStarred(Database::UserId userId, Database::ArtistId artistId) override; bool isStarred(Database::UserId userId, Database::ArtistId artistId) override;
ArtistContainer getStarredArtists(Database::UserId userId, Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) override;
const std::vector<Database::ClusterId>& clusterIds, ArtistContainer getStarredArtists(Database::UserId userId,
std::optional<Database::TrackArtistLinkType> linkType, const std::vector<Database::ClusterId>& clusterIds,
Database::ArtistSortMethod sortMethod, std::optional<Database::TrackArtistLinkType> linkType,
Database::Range range) override; Database::ArtistSortMethod sortMethod,
Database::Range range) override;
void star(Database::UserId userId, Database::ReleaseId releaseId) override; void star(Database::UserId userId, Database::ReleaseId releaseId) override;
void unstar(Database::UserId userId, Database::ReleaseId releaseId) override; void unstar(Database::UserId userId, Database::ReleaseId releaseId) override;
bool isStarred(Database::UserId userId, Database::ReleaseId artistId) override; bool isStarred(Database::UserId userId, Database::ReleaseId releasedId) override;
ReleaseContainer getStarredReleases(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) override; Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ReleaseId releasedId) override;
ReleaseContainer getStarredReleases(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) override;
void star(Database::UserId userId, Database::TrackId trackId) override; void star(Database::UserId userId, Database::TrackId trackId) override;
void unstar(Database::UserId userId, Database::TrackId trackId) override; void unstar(Database::UserId userId, Database::TrackId trackId) override;
bool isStarred(Database::UserId userId, Database::TrackId trackId) override; bool isStarred(Database::UserId userId, Database::TrackId trackId) override;
TrackContainer getStarredTracks(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) override; Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::TrackId trackId) override;
TrackContainer getStarredTracks(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) override;
std::optional<Database::Scrobbler> getUserScrobbler(Database::UserId userId); std::optional<Database::Scrobbler> getUserScrobbler(Database::UserId userId);
template <typename ObjType, typename ObjIdType, typename StarredObjType> template <typename ObjType, typename ObjIdType, typename StarredObjType>
void star(Database::UserId userId, ObjIdType id); void star(Database::UserId userId, ObjIdType id);
template <typename ObjType, typename ObjIdType, typename StarredObjType> template <typename ObjType, typename ObjIdType, typename StarredObjType>
void unstar(Database::UserId userId, ObjIdType id); void unstar(Database::UserId userId, ObjIdType id);
template <typename ObjType, typename ObjIdType, typename StarredObjType> template <typename ObjType, typename ObjIdType, typename StarredObjType>
bool isStarred(Database::UserId userId, ObjIdType id); bool isStarred(Database::UserId userId, ObjIdType id);
template <typename ObjType, typename ObjIdType, typename StarredObjType>
Wt::WDateTime getStarredDateTime(Database::UserId userId, ObjIdType id);
Database::Db& _db; Database::Db& _db;
std::unordered_map<Database::Scrobbler, std::unique_ptr<IScrobbler>> _scrobblers; std::unordered_map<Database::Scrobbler, std::unique_ptr<IScrobbler>> _scrobblers;
}; };
} // ns Scrobbling } // ns Scrobbling
@@ -25,76 +25,90 @@
namespace Scrobbling namespace Scrobbling
{ {
using namespace Database; using namespace Database;
template <typename ObjType, typename ObjIdType, typename StarredObjType> template <typename ObjType, typename ObjIdType, typename StarredObjType>
void void ScrobblingService::star(UserId userId, ObjIdType objId)
ScrobblingService::star(UserId userId, ObjIdType objId) {
{ auto scrobbler {getUserScrobbler(userId)};
auto scrobbler {getUserScrobbler(userId)}; if (!scrobbler)
if (!scrobbler) return;
return;
typename StarredObjType::IdType starredObjId; typename StarredObjType::IdType starredObjId;
{ {
Session& session {_db.getTLSSession()}; Session& session {_db.getTLSSession()};
auto transaction {session.createUniqueTransaction()}; auto transaction {session.createUniqueTransaction()};
typename StarredObjType::pointer starredObj {StarredObjType::find(session, objId, userId, *scrobbler)}; typename StarredObjType::pointer starredObj {StarredObjType::find(session, objId, userId, *scrobbler)};
if (!starredObj) if (!starredObj)
{ {
const typename ObjType::pointer obj {ObjType::find(session, objId)}; const typename ObjType::pointer obj {ObjType::find(session, objId)};
if (!obj) if (!obj)
return; return;
const User::pointer user {User::find(session, userId)}; const User::pointer user {User::find(session, userId)};
if (!user) if (!user)
return; return;
starredObj = session.create<StarredObjType>(obj, user, *scrobbler); starredObj = session.create<StarredObjType>(obj, user, *scrobbler);
} }
starredObj.modify()->setDateTime(Wt::WDateTime::currentDateTime()); starredObj.modify()->setDateTime(Wt::WDateTime::currentDateTime());
starredObjId = starredObj->getId(); starredObjId = starredObj->getId();
} }
_scrobblers[*scrobbler]->onStarred(starredObjId); _scrobblers[*scrobbler]->onStarred(starredObjId);
} }
template <typename ObjType, typename ObjIdType, typename StarredObjType> template <typename ObjType, typename ObjIdType, typename StarredObjType>
void void ScrobblingService::unstar(UserId userId, ObjIdType objId)
ScrobblingService::unstar(UserId userId, ObjIdType objId) {
{ auto scrobbler {getUserScrobbler(userId)};
auto scrobbler {getUserScrobbler(userId)}; if (!scrobbler)
if (!scrobbler) return;
return;
typename StarredObjType::IdType starredObjId; typename StarredObjType::IdType starredObjId;
{ {
Session& session {_db.getTLSSession()}; Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()}; auto transaction {session.createSharedTransaction()};
typename StarredObjType::pointer starredObj {StarredObjType::find(session, objId, userId, *scrobbler)}; typename StarredObjType::pointer starredObj {StarredObjType::find(session, objId, userId, *scrobbler)};
if (!starredObj) if (!starredObj)
return; return;
starredObjId = starredObj->getId(); starredObjId = starredObj->getId();
} }
_scrobblers[*scrobbler]->onUnstarred(starredObjId); _scrobblers[*scrobbler]->onUnstarred(starredObjId);
} }
template <typename ObjType, typename ObjIdType, typename StarredObjType> template <typename ObjType, typename ObjIdType, typename StarredObjType>
bool bool ScrobblingService::isStarred(UserId userId, ObjIdType objId)
ScrobblingService::isStarred(UserId userId, ObjIdType objId) {
{ auto scrobbler {getUserScrobbler(userId)};
auto scrobbler {getUserScrobbler(userId)}; if (!scrobbler)
if (!scrobbler) return false;
return false;
Session& session {_db.getTLSSession()}; Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()}; auto transaction {session.createSharedTransaction()};
typename StarredObjType::pointer starredObj {StarredObjType::find(session, objId, userId, *scrobbler)}; typename StarredObjType::pointer starredObj {StarredObjType::find(session, objId, userId, *scrobbler)};
return starredObj && (starredObj->getScrobblingState() != ScrobblingState::PendingRemove); return starredObj && (starredObj->getScrobblingState() != ScrobblingState::PendingRemove);
} }
template <typename ObjType, typename ObjIdType, typename StarredObjType>
Wt::WDateTime ScrobblingService::getStarredDateTime(UserId userId, ObjIdType objId)
{
auto scrobbler {getUserScrobbler(userId)};
if (!scrobbler)
return {};
Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()};
typename StarredObjType::pointer starredObj {StarredObjType::find(session, objId, userId, *scrobbler)};
if (starredObj && (starredObj->getScrobblingState() != ScrobblingState::PendingRemove))
return starredObj->getDateTime();
return {};
}
} // ns Scrobbling } // ns Scrobbling
@@ -30,101 +30,93 @@
namespace namespace
{ {
template <typename StarredObjType> template <typename StarredObjType>
void onStarred(Database::Session& session, typename StarredObjType::IdType id) void onStarred(Database::Session& session, typename StarredObjType::IdType id)
{ {
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
if (auto starredObj {StarredObjType::find(session, id)}) if (auto starredObj{ StarredObjType::find(session, id) })
starredObj.modify()->setScrobblingState(Database::ScrobblingState::Synchronized); starredObj.modify()->setScrobblingState(Database::ScrobblingState::Synchronized);
} }
template <typename StarredObjType> template <typename StarredObjType>
void onUnstarred(Database::Session& session, typename StarredObjType::IdType id) void onUnstarred(Database::Session& session, typename StarredObjType::IdType id)
{ {
auto transaction {session.createUniqueTransaction()}; auto transaction{ session.createUniqueTransaction() };
if (auto starredObj {StarredObjType::find(session, id)}) if (auto starredObj{ StarredObjType::find(session, id) })
starredObj.remove(); starredObj.remove();
} }
} }
namespace Scrobbling namespace Scrobbling
{ {
InternalScrobbler::InternalScrobbler(Database::Db& db) InternalScrobbler::InternalScrobbler(Database::Db& db)
: _db {db} : _db{ db }
{} {}
void void InternalScrobbler::listenStarted(const Listen&)
InternalScrobbler::listenStarted(const Listen&) {
{ // nothing to do
// nothing to do }
}
void void InternalScrobbler::listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration)
InternalScrobbler::listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) {
{ // only record tracks that have been played for at least of few seconds...
// only record tracks that have been played for at least of few seconds... if (duration && *duration < std::chrono::seconds{ 5 })
if (duration && *duration < std::chrono::seconds {5}) return;
return;
addTimedListen({listen, Wt::WDateTime::currentDateTime()}); addTimedListen({ listen, Wt::WDateTime::currentDateTime() });
} }
void void InternalScrobbler::addTimedListen(const TimedListen& listen)
InternalScrobbler::addTimedListen(const TimedListen& listen) {
{ Database::Session& session{ _db.getTLSSession() };
Database::Session& session {_db.getTLSSession()}; auto transaction{ session.createUniqueTransaction() };
auto transaction {session.createUniqueTransaction()};
if (Database::Listen::find(session, listen.userId, listen.trackId, Database::Scrobbler::Internal, listen.listenedAt)) if (Database::Listen::find(session, listen.userId, listen.trackId, Database::Scrobbler::Internal, listen.listenedAt))
return; return;
const Database::User::pointer user {Database::User::find(session, listen.userId)}; const Database::User::pointer user{ Database::User::find(session, listen.userId) };
if (!user) if (!user)
return; return;
const Database::Track::pointer track {Database::Track::find(session, listen.trackId)}; const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) };
if (!track) if (!track)
return; return;
auto dbListen {session.create<Database::Listen>(user, track, Database::Scrobbler::Internal, listen.listenedAt)}; auto dbListen{ session.create<Database::Listen>(user, track, Database::Scrobbler::Internal, listen.listenedAt) };
dbListen.modify()->setScrobblingState(Database::ScrobblingState::Synchronized); dbListen.modify()->setScrobblingState(Database::ScrobblingState::Synchronized);
} }
void void InternalScrobbler::onStarred(Database::StarredArtistId starredArtistId)
InternalScrobbler::onStarred(Database::StarredArtistId starredArtistId) {
{ ::onStarred<Database::StarredArtist>(_db.getTLSSession(), starredArtistId);
::onStarred<Database::StarredArtist>(_db.getTLSSession(), starredArtistId); }
}
void void InternalScrobbler::onUnstarred(Database::StarredArtistId starredArtistId)
InternalScrobbler::onUnstarred(Database::StarredArtistId starredArtistId) {
{ ::onUnstarred<Database::StarredArtist>(_db.getTLSSession(), starredArtistId);
::onUnstarred<Database::StarredArtist>(_db.getTLSSession(), starredArtistId); }
}
void void InternalScrobbler::onStarred(Database::StarredReleaseId starredReleaseId)
InternalScrobbler::onStarred(Database::StarredReleaseId starredReleaseId) {
{ ::onStarred<Database::StarredRelease>(_db.getTLSSession(), starredReleaseId);
::onStarred<Database::StarredRelease>(_db.getTLSSession(), starredReleaseId); }
}
void InternalScrobbler::onUnstarred(Database::StarredReleaseId starredReleaseId) void InternalScrobbler::onUnstarred(Database::StarredReleaseId starredReleaseId)
{ {
::onUnstarred<Database::StarredRelease>(_db.getTLSSession(), starredReleaseId); ::onUnstarred<Database::StarredRelease>(_db.getTLSSession(), starredReleaseId);
} }
void void InternalScrobbler::onStarred(Database::StarredTrackId starredTrackId)
InternalScrobbler::onStarred(Database::StarredTrackId starredTrackId) {
{ ::onStarred<Database::StarredTrack>(_db.getTLSSession(), starredTrackId);
::onStarred<Database::StarredTrack>(_db.getTLSSession(), starredTrackId); }
}
void void InternalScrobbler::onUnstarred(Database::StarredTrackId starredTrackId)
InternalScrobbler::onUnstarred(Database::StarredTrackId starredTrackId) {
{ ::onUnstarred<Database::StarredTrack>(_db.getTLSSession(), starredTrackId);
::onUnstarred<Database::StarredTrack>(_db.getTLSSession(), starredTrackId); }
}
} // Scrobbling } // Scrobbling
@@ -23,30 +23,30 @@
namespace Database namespace Database
{ {
class Db; class Db;
} }
namespace Scrobbling namespace Scrobbling
{ {
class InternalScrobbler final : public IScrobbler class InternalScrobbler final : public IScrobbler
{ {
public: public:
InternalScrobbler(Database::Db& db); InternalScrobbler(Database::Db& db);
private: private:
// IScrobbler // IScrobbler
void listenStarted(const Listen& listen) override; void listenStarted(const Listen& listen) override;
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override; void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
void addTimedListen(const TimedListen& listen) override; void addTimedListen(const TimedListen& listen) override;
void onStarred(Database::StarredArtistId) override; void onStarred(Database::StarredArtistId) override;
void onUnstarred(Database::StarredArtistId) override; void onUnstarred(Database::StarredArtistId) override;
void onStarred(Database::StarredReleaseId) override; void onStarred(Database::StarredReleaseId) override;
void onUnstarred(Database::StarredReleaseId) override; void onUnstarred(Database::StarredReleaseId) override;
void onStarred(Database::StarredTrackId) override; void onStarred(Database::StarredTrackId) override;
void onUnstarred(Database::StarredTrackId) override; void onUnstarred(Database::StarredTrackId) override;
Database::Db& _db; Database::Db& _db;
}; };
} // Scrobbling } // Scrobbling
@@ -23,9 +23,9 @@
namespace Scrobbling namespace Scrobbling
{ {
class Exception : public LmsException class Exception : public LmsException
{ {
public: public:
using LmsException::LmsException; using LmsException::LmsException;
}; };
} }
@@ -19,11 +19,11 @@
#pragma once #pragma once
#include <boost/asio/io_service.hpp>
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <boost/asio/io_service.hpp>
#include <Wt/WDateTime.h>
#include "services/scrobbling/Listen.hpp" #include "services/scrobbling/Listen.hpp"
#include "services/database/ArtistId.hpp" #include "services/database/ArtistId.hpp"
@@ -34,77 +34,80 @@
namespace Database namespace Database
{ {
class Db; class Db;
} }
namespace Scrobbling namespace Scrobbling
{ {
class IScrobblingService class IScrobblingService
{ {
public: public:
virtual ~IScrobblingService() = default; virtual ~IScrobblingService() = default;
// Scrobbling // Scrobbling
virtual void listenStarted(const Listen& listen) = 0; virtual void listenStarted(const Listen& listen) = 0;
virtual void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> playedDuration = std::nullopt) = 0; virtual void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> playedDuration = std::nullopt) = 0;
virtual void addTimedListen(const TimedListen& listen) = 0; virtual void addTimedListen(const TimedListen& listen) = 0;
// Stats // Stats
using ArtistContainer = Database::RangeResults<Database::ArtistId>; using ArtistContainer = Database::RangeResults<Database::ArtistId>;
using ReleaseContainer = Database::RangeResults<Database::ReleaseId>; using ReleaseContainer = Database::RangeResults<Database::ReleaseId>;
using TrackContainer = Database::RangeResults<Database::TrackId>; using TrackContainer = Database::RangeResults<Database::TrackId>;
// From most recent to oldest // From most recent to oldest
virtual ArtistContainer getRecentArtists(Database::UserId userId, virtual ArtistContainer getRecentArtists(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType, std::optional<Database::TrackArtistLinkType> linkType,
Database::Range range) = 0; Database::Range range) = 0;
virtual ReleaseContainer getRecentReleases(Database::UserId userId, virtual ReleaseContainer getRecentReleases(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
Database::Range range) = 0; Database::Range range) = 0;
virtual TrackContainer getRecentTracks(Database::UserId userId, virtual TrackContainer getRecentTracks(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
Database::Range range) = 0; Database::Range range) = 0;
// Top // Top
virtual ArtistContainer getTopArtists(Database::UserId userId, virtual ArtistContainer getTopArtists(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
std::optional<Database::TrackArtistLinkType> linkType, std::optional<Database::TrackArtistLinkType> linkType,
Database::Range) = 0; Database::Range) = 0;
virtual ReleaseContainer getTopReleases(Database::UserId userId, virtual ReleaseContainer getTopReleases(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
Database::Range range) = 0; Database::Range range) = 0;
virtual TrackContainer getTopTracks(Database::UserId userId, virtual TrackContainer getTopTracks(Database::UserId userId,
const std::vector<Database::ClusterId>& clusterIds, const std::vector<Database::ClusterId>& clusterIds,
Database::Range range) = 0; Database::Range range) = 0;
// Star // Star
virtual void star(Database::UserId userId, Database::ArtistId artistId) = 0; virtual void star(Database::UserId userId, Database::ArtistId artistId) = 0;
virtual void unstar(Database::UserId userId, Database::ArtistId artistId) = 0; virtual void unstar(Database::UserId userId, Database::ArtistId artistId) = 0;
virtual bool isStarred(Database::UserId userId, Database::ArtistId artistId) = 0; virtual bool isStarred(Database::UserId userId, Database::ArtistId artistId) = 0;
virtual ArtistContainer getStarredArtists(Database::UserId userId, virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) = 0;
const std::vector<Database::ClusterId>& clusterIds, virtual ArtistContainer getStarredArtists(Database::UserId userId,
std::optional<Database::TrackArtistLinkType> linkType, const std::vector<Database::ClusterId>& clusterIds,
Database::ArtistSortMethod sortMethod, std::optional<Database::TrackArtistLinkType> linkType,
Database::Range range) = 0; Database::ArtistSortMethod sortMethod,
Database::Range range) = 0;
virtual void star(Database::UserId userId, Database::ReleaseId releaseId) = 0; virtual void star(Database::UserId userId, Database::ReleaseId releaseId) = 0;
virtual void unstar(Database::UserId userId, Database::ReleaseId releaseId) = 0; virtual void unstar(Database::UserId userId, Database::ReleaseId releaseId) = 0;
virtual bool isStarred(Database::UserId userId, Database::ReleaseId artistId) = 0; virtual bool isStarred(Database::UserId userId, Database::ReleaseId artistId) = 0;
virtual ReleaseContainer getStarredReleases(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0; virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ReleaseId artistId) = 0;
virtual ReleaseContainer getStarredReleases(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0;
virtual void star(Database::UserId userId, Database::TrackId trackId) = 0; virtual void star(Database::UserId userId, Database::TrackId trackId) = 0;
virtual void unstar(Database::UserId userId, Database::TrackId trackId) = 0; virtual void unstar(Database::UserId userId, Database::TrackId trackId) = 0;
virtual bool isStarred(Database::UserId userId, Database::TrackId artistId) = 0; virtual bool isStarred(Database::UserId userId, Database::TrackId artistId) = 0;
virtual TrackContainer getStarredTracks(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0; virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::TrackId artistId) = 0;
}; virtual TrackContainer getStarredTracks(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0;
};
std::unique_ptr<IScrobblingService> createScrobblingService(boost::asio::io_service& ioService, Database::Db& db); std::unique_ptr<IScrobblingService> createScrobblingService(boost::asio::io_service& ioService, Database::Db& db);
} // ns Scrobbling } // ns Scrobbling
@@ -26,15 +26,15 @@
namespace Scrobbling namespace Scrobbling
{ {
struct Listen struct Listen
{ {
Database::UserId userId {}; Database::UserId userId{};
Database::TrackId trackId {}; Database::TrackId trackId{};
}; };
struct TimedListen : public Listen struct TimedListen : public Listen
{ {
Wt::WDateTime listenedAt; Wt::WDateTime listenedAt;
}; };
} // ns Scrobbling } // ns Scrobbling
+2 -4
View File
@@ -32,8 +32,6 @@
namespace API::Subsonic namespace API::Subsonic
{ {
static const std::string_view reportedDummyStarredDate{ "2000-01-01T00:00:00" };
using namespace Database; using namespace Database;
Response::Node createAlbumNode(const Release::pointer& release, Session& dbSession, const User::pointer& user, bool id3) Response::Node createAlbumNode(const Release::pointer& release, Session& dbSession, const User::pointer& user, bool id3)
@@ -95,8 +93,8 @@ namespace API::Subsonic
} }
} }
if (Service<Scrobbling::IScrobblingService>::get()->isStarred(user->getId(), release->getId())) if (const Wt::WDateTime dateTime{ Service<Scrobbling::IScrobblingService>::get()->getStarredDateTime(user->getId(), release->getId()) }; dateTime.isValid())
albumNode.setAttribute("starred", reportedDummyStarredDate); // TODO report correct date/time albumNode.setAttribute("starred", StringUtils::toISO8601String(dateTime)); // TODO report correct date/time
return albumNode; return albumNode;
} }
+3 -4
View File
@@ -24,13 +24,12 @@
#include "services/database/User.hpp" #include "services/database/User.hpp"
#include "services/scrobbling/IScrobblingService.hpp" #include "services/scrobbling/IScrobblingService.hpp"
#include "utils/Service.hpp" #include "utils/Service.hpp"
#include "utils/String.hpp"
#include "SubsonicId.hpp" #include "SubsonicId.hpp"
namespace API::Subsonic namespace API::Subsonic
{ {
static const std::string_view reportedDummyStarredDate{ "2000-01-01T00:00:00" };
using namespace Database; using namespace Database;
namespace Utils namespace Utils
@@ -66,8 +65,8 @@ namespace API::Subsonic
artistNode.setAttribute("albumCount", releases.results.size()); artistNode.setAttribute("albumCount", releases.results.size());
} }
if (Service<Scrobbling::IScrobblingService>::get()->isStarred(user->getId(), artist->getId())) if (const Wt::WDateTime dateTime{ Service<Scrobbling::IScrobblingService>::get()->getStarredDateTime(user->getId(), artist->getId()) }; dateTime.isValid())
artistNode.setAttribute("starred", reportedDummyStarredDate); // TODO handle date/time artistNode.setAttribute("starred", StringUtils::toISO8601String(dateTime));
return artistNode; return artistNode;
} }
+2 -4
View File
@@ -37,8 +37,6 @@ namespace API::Subsonic
{ {
using namespace Database; using namespace Database;
static const std::string_view reportedDummyStarredDate{ "2000-01-01T00:00:00" };
namespace namespace
{ {
std::string_view formatToSuffix(AudioFormat format) std::string_view formatToSuffix(AudioFormat format)
@@ -143,8 +141,8 @@ namespace API::Subsonic
trackResponse.setAttribute("type", "music"); trackResponse.setAttribute("type", "music");
trackResponse.setAttribute("created", StringUtils::toISO8601String(track->getLastWritten())); trackResponse.setAttribute("created", StringUtils::toISO8601String(track->getLastWritten()));
if (Service<Scrobbling::IScrobblingService>::get()->isStarred(user->getId(), track->getId())) if (const Wt::WDateTime dateTime{ Service<Scrobbling::IScrobblingService>::get()->getStarredDateTime(user->getId(), track->getId()) }; dateTime.isValid())
trackResponse.setAttribute("starred", reportedDummyStarredDate); // TODO handle date/time trackResponse.setAttribute("starred", StringUtils::toISO8601String(dateTime));
// Report the first GENRE for this track // Report the first GENRE for this track
ClusterType::pointer clusterType{ ClusterType::find(dbSession, "GENRE") }; ClusterType::pointer clusterType{ ClusterType::find(dbSession, "GENRE") };
+2
View File
@@ -288,11 +288,13 @@ namespace StringUtils
std::string toISO8601String(const Wt::WDateTime& dateTime) std::string toISO8601String(const Wt::WDateTime& dateTime)
{ {
// assume UTC
return dateTime.toString("yyyy-MM-ddThh:mm:ss.zzz", false).toUTF8(); return dateTime.toString("yyyy-MM-ddThh:mm:ss.zzz", false).toUTF8();
} }
std::string toISO8601String(const Wt::WDate& date) std::string toISO8601String(const Wt::WDate& date)
{ {
// assume UTC
return date.toString("yyyy-MM-dd").toUTF8(); return date.toString("yyyy-MM-dd").toUTF8();
} }
} // StringUtils } // StringUtils