API Subsonic: Adding playlist support
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ struct Id
|
||||
Track,
|
||||
Release,
|
||||
Artist,
|
||||
Playlist,
|
||||
};
|
||||
|
||||
Type type;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user