API Subsonic: Adding playlist support

This commit is contained in:
emeric
2019-04-12 14:00:05 +02:00
parent 366420013f
commit fdbac7f444
10 changed files with 555 additions and 228 deletions
+11 -6
View File
@@ -41,12 +41,14 @@ IdFromString(const std::string& id)
Id res; Id res;
std::string type {std::move(values[0])}; std::string type {std::move(values[0])};
if (type == "artist") if (type == "ar")
res.type = Id::Type::Artist; res.type = Id::Type::Artist;
else if (type == "album") else if (type == "al")
res.type = Id::Type::Release; res.type = Id::Type::Release;
else if (type == "track") else if (type == "tr")
res.type = Id::Type::Track; res.type = Id::Type::Track;
else if (type == "pl")
res.type = Id::Type::Playlist;
else else
{ {
LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format"; LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format";
@@ -75,13 +77,16 @@ IdToString(const Id& id)
case Id::Type::Root: case Id::Type::Root:
return "root"; return "root";
case Id::Type::Artist: case Id::Type::Artist:
res = "artist-"; res = "ar-";
break; break;
case Id::Type::Release: case Id::Type::Release:
res = "album-"; res = "al-";
break; break;
case Id::Type::Track: case Id::Type::Track:
res = "track-"; res = "tr-";
break;
case Id::Type::Playlist:
res = "pl-";
break; break;
} }
+1
View File
@@ -32,6 +32,7 @@ struct Id
Track, Track,
Release, Release,
Artist, Artist,
Playlist,
}; };
Type type; Type type;
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -180,10 +180,21 @@ Handler::getUser(const Wt::Auth::User& authUser)
return authInfo->user(); return authInfo->user();
} }
User::pointer
Handler::getUser(const std::string& loginName)
{
auto authUser {getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, loginName)};
if (!authUser.isValid())
return User::pointer {};
return getUser(authUser);
}
User::pointer User::pointer
Handler::createUser(const Wt::Auth::User& authUser) Handler::createUser(const Wt::Auth::User& authUser)
{ {
if (!authUser.isValid()) { if (!authUser.isValid())
{
LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser"; LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser";
return User::pointer(); return User::pointer();
} }
+1
View File
@@ -46,6 +46,7 @@ class Handler
Wt::Dbo::Session& getSession() { return _session; } Wt::Dbo::Session& getSession() { return _session; }
Wt::Dbo::ptr<User> getCurrentUser(); // get the current user, may return empty Wt::Dbo::ptr<User> getCurrentUser(); // get the current user, may return empty
Wt::Dbo::ptr<User> getUser(const std::string& loginName);
Wt::Dbo::ptr<User> getUser(const Wt::Auth::User& authUser); Wt::Dbo::ptr<User> getUser(const Wt::Auth::User& authUser);
Wt::Dbo::ptr<User> createUser(const Wt::Auth::User& authUser); Wt::Dbo::ptr<User> createUser(const Wt::Auth::User& authUser);
+52 -27
View File
@@ -31,26 +31,21 @@
namespace Database { namespace Database {
TrackList::TrackList() TrackList::TrackList(const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user)
: _isPublic(false) : _name {name},
{ _type {type},
_isPublic {isPublic},
} _user {user}
TrackList::TrackList(std::string name, bool isPublic, Wt::Dbo::ptr<User> user)
: _name(name),
_isPublic(isPublic),
_user(user)
{ {
} }
TrackList::pointer TrackList::pointer
TrackList::create(Wt::Dbo::Session& session, std::string name, bool isPublic, Wt::Dbo::ptr<User> user) TrackList::create(Wt::Dbo::Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user)
{ {
assert(user); assert(user);
auto res = session.add( std::make_unique<TrackList>(name, isPublic, user) ); auto res = session.add( std::make_unique<TrackList>(name, type, isPublic, user) );
session.flush(); session.flush();
return res; return res;
@@ -66,15 +61,31 @@ TrackList::add(IdType trackId)
} }
TrackList::pointer TrackList::pointer
TrackList::get(Wt::Dbo::Session& session, std::string name, Wt::Dbo::ptr<User> user) TrackList::get(Wt::Dbo::Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user)
{ {
return session.find<TrackList>().where("name = ? AND user_id = ?").bind(name).bind(user.id()); return session.find<TrackList>()
.where("name = ?").bind(name)
.where("type = ?").bind(type)
.where("user_id = ?").bind(user.id());
} }
std::vector<TrackList::pointer> std::vector<TrackList::pointer>
TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user) TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user)
{ {
Wt::Dbo::collection<TrackList::pointer> res = session.find<TrackList>().where("user_id = ?").bind(user.id()).orderBy("name"); Wt::Dbo::collection<TrackList::pointer> res = session.find<TrackList>()
.where("user_id = ?").bind(user.id())
.orderBy("name COLLATE NOCASE");
return std::vector<TrackList::pointer>(res.begin(), res.end());
}
std::vector<TrackList::pointer>
TrackList::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user, Type type)
{
Wt::Dbo::collection<TrackList::pointer> res = session.find<TrackList>()
.where("user_id = ?").bind(user.id())
.where("type = ?").bind(type)
.orderBy("name COLLATE NOCASE");
return std::vector<TrackList::pointer>(res.begin(), res.end()); return std::vector<TrackList::pointer>(res.begin(), res.end());
} }
@@ -87,7 +98,7 @@ TrackList::getById(Wt::Dbo::Session& session, IdType id)
std::vector<Wt::Dbo::ptr<TrackListEntry>> std::vector<Wt::Dbo::ptr<TrackListEntry>>
TrackList::getEntries(int offset, int size) const TrackList::getEntries(boost::optional<std::size_t> offset, boost::optional<std::size_t> size) const
{ {
assert(session()); assert(session());
assert(IdIsValid(self()->id())); assert(IdIsValid(self()->id()));
@@ -96,14 +107,14 @@ TrackList::getEntries(int offset, int size) const
session()->find<TrackListEntry>() session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(self().id()) .where("tracklist_id = ?").bind(self().id())
.orderBy("id") .orderBy("id")
.limit(size) .limit(size ? static_cast<int>(*size) : -1)
.offset(offset); .offset(offset ? static_cast<int>(*offset) : -1);
return std::vector<Wt::Dbo::ptr<TrackListEntry>>(entries.begin(), entries.end()); return std::vector<Wt::Dbo::ptr<TrackListEntry>>(entries.begin(), entries.end());
} }
std::vector<Wt::Dbo::ptr<TrackListEntry>> std::vector<Wt::Dbo::ptr<TrackListEntry>>
TrackList::getEntriesReverse(int offset, int size) const TrackList::getEntriesReverse(boost::optional<std::size_t> offset, boost::optional<std::size_t> size) const
{ {
assert(session()); assert(session());
assert(IdIsValid(self()->id())); assert(IdIsValid(self()->id()));
@@ -112,8 +123,8 @@ TrackList::getEntriesReverse(int offset, int size) const
session()->find<TrackListEntry>() session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(self().id()) .where("tracklist_id = ?").bind(self().id())
.orderBy("id DESC") .orderBy("id DESC")
.limit(size) .limit(size ? static_cast<int>(*size) : -1)
.offset(offset); .offset(offset ? static_cast<int>(*offset) : -1);
return std::vector<Wt::Dbo::ptr<TrackListEntry>>(entries.begin(), entries.end()); return std::vector<Wt::Dbo::ptr<TrackListEntry>>(entries.begin(), entries.end());
} }
@@ -175,6 +186,20 @@ TrackList::getTrackIds() const
return std::vector<IdType>(res.begin(), res.end()); return std::vector<IdType>(res.begin(), res.end());
} }
std::chrono::milliseconds
TrackList::getDuration() const
{
assert(session());
assert(IdIsValid(self()->id()));
using milli = std::chrono::duration<int, std::milli>;
Wt::Dbo::Query<milli> query {session()->query<milli>("SELECT SUM(duration) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id")
.where("p_e.tracklist_id = ?").bind(self()->id())};
return query.resultValue();
}
void void
TrackList::shuffle() TrackList::shuffle()
{ {
@@ -193,7 +218,7 @@ TrackList::shuffle()
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
TrackList::getTopArtists(int limit) const TrackList::getTopArtists(std::size_t limit) const
{ {
assert(session()); assert(session());
assert(IdIsValid(self()->id())); assert(IdIsValid(self()->id()));
@@ -202,13 +227,13 @@ TrackList::getTopArtists(int limit) const
.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")
.limit(limit); .limit(static_cast<int>(limit));
return std::vector<Artist::pointer>(res.begin(), res.end()); return std::vector<Artist::pointer>(res.begin(), res.end());
} }
std::vector<Release::pointer> std::vector<Release::pointer>
TrackList::getTopReleases(int limit) const TrackList::getTopReleases(std::size_t limit) const
{ {
assert(session()); assert(session());
assert(IdIsValid(self()->id())); assert(IdIsValid(self()->id()));
@@ -217,13 +242,13 @@ TrackList::getTopReleases(int limit) const
.where("p.id = ?").bind(self()->id()) .where("p.id = ?").bind(self()->id())
.groupBy("r.id") .groupBy("r.id")
.orderBy("COUNT(r.id) DESC") .orderBy("COUNT(r.id) DESC")
.limit(limit); .limit(static_cast<int>(limit));
return std::vector<Release::pointer>(res.begin(), res.end()); return std::vector<Release::pointer>(res.begin(), res.end());
} }
std::vector<Track::pointer> std::vector<Track::pointer>
TrackList::getTopTracks(int limit) const TrackList::getTopTracks(std::size_t limit) const
{ {
assert(session()); assert(session());
assert(IdIsValid(self()->id())); assert(IdIsValid(self()->id()));
@@ -232,7 +257,7 @@ TrackList::getTopTracks(int limit) const
.where("p.id = ?").bind(self()->id()) .where("p.id = ?").bind(self()->id())
.groupBy("t.id") .groupBy("t.id")
.orderBy("COUNT(t.id) DESC") .orderBy("COUNT(t.id) DESC")
.limit(limit); .limit(static_cast<int>(limit));
return std::vector<Track::pointer>(res.begin(), res.end()); return std::vector<Track::pointer>(res.begin(), res.end());
} }
+29 -10
View File
@@ -19,6 +19,8 @@
#pragma once #pragma once
#include <boost/optional.hpp>
#include <Wt/Dbo/Dbo.h> #include <Wt/Dbo/Dbo.h>
#include <string> #include <string>
@@ -39,27 +41,38 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
public: public:
using pointer = Wt::Dbo::ptr<TrackList>; using pointer = Wt::Dbo::ptr<TrackList>;
TrackList(); enum class Type
TrackList(std::string name, bool isPublic, Wt::Dbo::ptr<User> user); {
Playlist, // user controlled playlists
Internal, // current playqueue, history
};
TrackList() = default;
TrackList(const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
// Stats utility // Stats utility
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(int limit = 1) const; std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(std::size_t limit = 1) const;
std::vector<Wt::Dbo::ptr<Release>> getTopReleases(int limit = 1) const; std::vector<Wt::Dbo::ptr<Release>> getTopReleases(std::size_t limit = 1) const;
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(int limit = 1) const; std::vector<Wt::Dbo::ptr<Track>> getTopTracks(std::size_t limit = 1) const;
// Search utility // Search utility
static pointer get(Wt::Dbo::Session& session, std::string name, Wt::Dbo::ptr<User> user); static pointer get(Wt::Dbo::Session& session, const std::string& name, Type type, Wt::Dbo::ptr<User> user);
static pointer getById(Wt::Dbo::Session& session, IdType tracklistId); static pointer getById(Wt::Dbo::Session& session, IdType tracklistId);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user); static std::vector<pointer> getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user);
static std::vector<pointer> getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user, Type type);
// Create utility // Create utility
static pointer create(Wt::Dbo::Session& session, std::string name, bool isPublic, Wt::Dbo::ptr<User> user); static pointer create(Wt::Dbo::Session& session, const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user);
// Accessors // Accessors
std::string getName() const { return _name; } std::string getName() const { return _name; }
bool isPublic() const { return _isPublic; } bool isPublic() const { return _isPublic; }
Type getType() const { return _type; }
Wt::Dbo::ptr<User> getUser() const { return _user; }
// Modifiers // Modifiers
void setName(const std::string& name) { _name = name; }
void setIsPublic(bool isPublic) { _isPublic = isPublic; }
Wt::Dbo::ptr<TrackListEntry> add(IdType trackId); Wt::Dbo::ptr<TrackListEntry> add(IdType trackId);
void clear() { _entries.clear(); } void clear() { _entries.clear(); }
void shuffle(); void shuffle();
@@ -67,11 +80,13 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
// Get tracks, ordered by position // Get tracks, ordered by position
std::size_t getCount() const; std::size_t getCount() const;
Wt::Dbo::ptr<TrackListEntry> getEntry(std::size_t pos) const; Wt::Dbo::ptr<TrackListEntry> getEntry(std::size_t pos) const;
std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntries(int offset = -1, int size = -1) const; std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntries(boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {}) const;
std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntriesReverse(int offset = -1, int size = -1) const; std::vector<Wt::Dbo::ptr<TrackListEntry>> getEntriesReverse(boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {}) const;
std::vector<IdType> getTrackIds() const; std::vector<IdType> getTrackIds() const;
std::chrono::milliseconds getDuration() const;
// Get clusters, order by occurence // Get clusters, order by occurence
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const; std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
@@ -81,7 +96,9 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
void persist(Action& a) void persist(Action& a)
{ {
Wt::Dbo::field(a, _name, "name"); Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _type, "type");
Wt::Dbo::field(a, _isPublic, "public"); Wt::Dbo::field(a, _isPublic, "public");
Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "tracklist"); Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "tracklist");
} }
@@ -89,7 +106,9 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
private: private:
std::string _name; std::string _name;
bool _isPublic; Type _type {Type::Playlist};
bool _isPublic {false};
Wt::Dbo::ptr<User> _user; Wt::Dbo::ptr<User> _user;
Wt::Dbo::collection< Wt::Dbo::ptr<TrackListEntry> > _entries; Wt::Dbo::collection< Wt::Dbo::ptr<TrackListEntry> > _entries;
+4 -4
View File
@@ -101,9 +101,9 @@ User::getPlayedTrackList() const
assert(IdIsValid(self()->id())); assert(IdIsValid(self()->id()));
assert(session()); assert(session());
auto res = TrackList::get(*session(), listName, self()); auto res = TrackList::get(*session(), listName, TrackList::Type::Internal, self());
if (!res) if (!res)
res = TrackList::create(*session(), listName, false, self()); res = TrackList::create(*session(), listName, TrackList::Type::Internal, false, self());
return res; return res;
} }
@@ -117,9 +117,9 @@ User::getQueuedTrackList() const
assert(IdIsValid(self()->id())); assert(IdIsValid(self()->id()));
assert(session()); assert(session());
auto res = TrackList::get(*session(), listName, self()); auto res = TrackList::get(*session(), listName, TrackList::Type::Internal, self());
if (!res) if (!res)
res = TrackList::create(*session(), listName, false, self()); res = TrackList::create(*session(), listName, TrackList::Type::Internal, false, self());
return res; return res;
} }
+3 -2
View File
@@ -145,14 +145,15 @@ PlayQueue::updateRadioBtn()
Database::TrackList::pointer Database::TrackList::pointer
PlayQueue::getTrackList() PlayQueue::getTrackList()
{ {
static const std::string currentPlayQueueName = "__current__playqueue__";
Database::TrackList::pointer res; Database::TrackList::pointer res;
if (LmsApp->getUser()->isDemo()) if (LmsApp->getUser()->isDemo())
{ {
static const std::string currentPlayQueueName = "__current__playqueue__";
if (!_tracklistId) if (!_tracklistId)
{ {
res = Database::TrackList::create(LmsApp->getDboSession(), currentPlayQueueName, false, LmsApp->getUser()); res = Database::TrackList::create(LmsApp->getDboSession(), currentPlayQueueName, Database::TrackList::Type::Internal, false, LmsApp->getUser());
LmsApp->getDboSession().flush(); LmsApp->getDboSession().flush();
_tracklistId = res.id(); _tracklistId = res.id();
return res; return res;
+7
View File
@@ -54,6 +54,13 @@ bool readList(const std::string& str, const std::string& separators, std::list<s
return !str.empty(); return !str.empty();
} }
template<>
boost::optional<std::string>
readAs(const std::string& str)
{
return str;
}
std::vector<std::string> std::vector<std::string>
splitString(const std::string& string, const std::string& separators) splitString(const std::string& string, const std::string& separators)
{ {