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;
std::string type {std::move(values[0])};
if (type == "artist")
if (type == "ar")
res.type = Id::Type::Artist;
else if (type == "album")
else if (type == "al")
res.type = Id::Type::Release;
else if (type == "track")
else if (type == "tr")
res.type = Id::Type::Track;
else if (type == "pl")
res.type = Id::Type::Playlist;
else
{
LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format";
@@ -75,13 +77,16 @@ IdToString(const Id& id)
case Id::Type::Root:
return "root";
case Id::Type::Artist:
res = "artist-";
res = "ar-";
break;
case Id::Type::Release:
res = "album-";
res = "al-";
break;
case Id::Type::Track:
res = "track-";
res = "tr-";
break;
case Id::Type::Playlist:
res = "pl-";
break;
}
+1
View File
@@ -32,6 +32,7 @@ struct Id
Track,
Release,
Artist,
Playlist,
};
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();
}
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
Handler::createUser(const Wt::Auth::User& authUser)
{
if (!authUser.isValid()) {
if (!authUser.isValid())
{
LMS_LOG(DB, ERROR) << "Handler::getUser: invalid authUser";
return User::pointer();
}
+1
View File
@@ -46,6 +46,7 @@ class Handler
Wt::Dbo::Session& getSession() { return _session; }
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> createUser(const Wt::Auth::User& authUser);
+52 -27
View File
@@ -31,26 +31,21 @@
namespace Database {
TrackList::TrackList()
: _isPublic(false)
{
}
TrackList::TrackList(std::string name, bool isPublic, Wt::Dbo::ptr<User> user)
: _name(name),
_isPublic(isPublic),
_user(user)
TrackList::TrackList(const std::string& name, Type type, bool isPublic, Wt::Dbo::ptr<User> user)
: _name {name},
_type {type},
_isPublic {isPublic},
_user {user}
{
}
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);
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();
return res;
@@ -66,15 +61,31 @@ TrackList::add(IdType trackId)
}
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>
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());
}
@@ -87,7 +98,7 @@ TrackList::getById(Wt::Dbo::Session& session, IdType id)
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(IdIsValid(self()->id()));
@@ -96,14 +107,14 @@ TrackList::getEntries(int offset, int size) const
session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(self().id())
.orderBy("id")
.limit(size)
.offset(offset);
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
return std::vector<Wt::Dbo::ptr<TrackListEntry>>(entries.begin(), entries.end());
}
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(IdIsValid(self()->id()));
@@ -112,8 +123,8 @@ TrackList::getEntriesReverse(int offset, int size) const
session()->find<TrackListEntry>()
.where("tracklist_id = ?").bind(self().id())
.orderBy("id DESC")
.limit(size)
.offset(offset);
.limit(size ? static_cast<int>(*size) : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
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());
}
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
TrackList::shuffle()
{
@@ -193,7 +218,7 @@ TrackList::shuffle()
}
std::vector<Artist::pointer>
TrackList::getTopArtists(int limit) const
TrackList::getTopArtists(std::size_t limit) const
{
assert(session());
assert(IdIsValid(self()->id()));
@@ -202,13 +227,13 @@ TrackList::getTopArtists(int limit) const
.where("p.id = ?").bind(self()->id())
.groupBy("a.id")
.orderBy("COUNT(a.id) DESC")
.limit(limit);
.limit(static_cast<int>(limit));
return std::vector<Artist::pointer>(res.begin(), res.end());
}
std::vector<Release::pointer>
TrackList::getTopReleases(int limit) const
TrackList::getTopReleases(std::size_t limit) const
{
assert(session());
assert(IdIsValid(self()->id()));
@@ -217,13 +242,13 @@ TrackList::getTopReleases(int limit) const
.where("p.id = ?").bind(self()->id())
.groupBy("r.id")
.orderBy("COUNT(r.id) DESC")
.limit(limit);
.limit(static_cast<int>(limit));
return std::vector<Release::pointer>(res.begin(), res.end());
}
std::vector<Track::pointer>
TrackList::getTopTracks(int limit) const
TrackList::getTopTracks(std::size_t limit) const
{
assert(session());
assert(IdIsValid(self()->id()));
@@ -232,7 +257,7 @@ TrackList::getTopTracks(int limit) const
.where("p.id = ?").bind(self()->id())
.groupBy("t.id")
.orderBy("COUNT(t.id) DESC")
.limit(limit);
.limit(static_cast<int>(limit));
return std::vector<Track::pointer>(res.begin(), res.end());
}
+29 -10
View File
@@ -19,6 +19,8 @@
#pragma once
#include <boost/optional.hpp>
#include <Wt/Dbo/Dbo.h>
#include <string>
@@ -39,27 +41,38 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
public:
using pointer = Wt::Dbo::ptr<TrackList>;
TrackList();
TrackList(std::string name, bool isPublic, Wt::Dbo::ptr<User> user);
enum class Type
{
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
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(int limit = 1) const;
std::vector<Wt::Dbo::ptr<Release>> getTopReleases(int limit = 1) const;
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(int limit = 1) const;
std::vector<Wt::Dbo::ptr<Artist>> getTopArtists(std::size_t limit = 1) const;
std::vector<Wt::Dbo::ptr<Release>> getTopReleases(std::size_t limit = 1) const;
std::vector<Wt::Dbo::ptr<Track>> getTopTracks(std::size_t limit = 1) const;
// 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 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
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
std::string getName() const { return _name; }
bool isPublic() const { return _isPublic; }
Type getType() const { return _type; }
Wt::Dbo::ptr<User> getUser() const { return _user; }
// Modifiers
void setName(const std::string& name) { _name = name; }
void setIsPublic(bool isPublic) { _isPublic = isPublic; }
Wt::Dbo::ptr<TrackListEntry> add(IdType trackId);
void clear() { _entries.clear(); }
void shuffle();
@@ -67,11 +80,13 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
// Get tracks, ordered by position
std::size_t getCount() 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>> getEntriesReverse(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(boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {}) const;
std::vector<IdType> getTrackIds() const;
std::chrono::milliseconds getDuration() const;
// Get clusters, order by occurence
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
@@ -81,7 +96,9 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
void persist(Action& a)
{
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::field(a, _type, "type");
Wt::Dbo::field(a, _isPublic, "public");
Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, _entries, Wt::Dbo::ManyToOne, "tracklist");
}
@@ -89,7 +106,9 @@ class TrackList : public Wt::Dbo::Dbo<TrackList>
private:
std::string _name;
bool _isPublic;
Type _type {Type::Playlist};
bool _isPublic {false};
Wt::Dbo::ptr<User> _user;
Wt::Dbo::collection< Wt::Dbo::ptr<TrackListEntry> > _entries;
+4 -4
View File
@@ -101,9 +101,9 @@ User::getPlayedTrackList() const
assert(IdIsValid(self()->id()));
assert(session());
auto res = TrackList::get(*session(), listName, self());
auto res = TrackList::get(*session(), listName, TrackList::Type::Internal, self());
if (!res)
res = TrackList::create(*session(), listName, false, self());
res = TrackList::create(*session(), listName, TrackList::Type::Internal, false, self());
return res;
}
@@ -117,9 +117,9 @@ User::getQueuedTrackList() const
assert(IdIsValid(self()->id()));
assert(session());
auto res = TrackList::get(*session(), listName, self());
auto res = TrackList::get(*session(), listName, TrackList::Type::Internal, self());
if (!res)
res = TrackList::create(*session(), listName, false, self());
res = TrackList::create(*session(), listName, TrackList::Type::Internal, false, self());
return res;
}
+3 -2
View File
@@ -145,14 +145,15 @@ PlayQueue::updateRadioBtn()
Database::TrackList::pointer
PlayQueue::getTrackList()
{
static const std::string currentPlayQueueName = "__current__playqueue__";
Database::TrackList::pointer res;
if (LmsApp->getUser()->isDemo())
{
static const std::string currentPlayQueueName = "__current__playqueue__";
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();
_tracklistId = res.id();
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();
}
template<>
boost::optional<std::string>
readAs(const std::string& str)
{
return str;
}
std::vector<std::string>
splitString(const std::string& string, const std::string& separators)
{