Added the ability for remote clients to create, rename, and delete

playlists. Note that this functionality was not added to the remote
clients yet, only to the core SDK and to the websocket plugin.

Specifically, updated ISimpleDataProvider interface with the following
methods:

  1. SavePlaylist()
  2. RenamePlaylist()
  3. DeletePlaylist()

Then added the following messages to the server plugin:

  1. save_playlist
  2. rename_playlist
  3. delete_playlist
This commit is contained in:
casey langen 2017-09-30 23:27:57 -07:00
parent 50f4551b2c
commit 0fcd05dc2c
12 changed files with 219 additions and 16 deletions

View File

@ -114,7 +114,7 @@ void LocalLibrary::Close() {
std::thread* thread = nullptr;
{
std::unique_lock<std::mutex> lock(this->mutex);
std::unique_lock<std::recursive_mutex> lock(this->mutex);
delete this->indexer;
this->indexer = nullptr;
@ -159,7 +159,7 @@ int LocalLibrary::Enqueue(QueryPtr query, unsigned int options, Callback callbac
LocalQueryPtr localQuery = std::dynamic_pointer_cast<LocalQuery>(query);
if (localQuery) {
std::unique_lock<std::mutex> lock(this->mutex);
std::unique_lock<std::recursive_mutex> lock(this->mutex);
if (this->exit) { /* closed */
return -1;
@ -189,7 +189,7 @@ int LocalLibrary::Enqueue(QueryPtr query, unsigned int options, Callback callbac
LocalLibrary::QueryContextPtr LocalLibrary::GetNextQuery() {
std::unique_lock<std::mutex> lock(this->mutex);
std::unique_lock<std::recursive_mutex> lock(this->mutex);
while (!this->queryQueue.size() && !this->exit) {
this->queueCondition.wait(lock);
}

View File

@ -72,7 +72,7 @@ namespace musik { namespace core { namespace library {
/* ILibrary */
virtual int Enqueue(
QueryPtr query,
QueryPtr query,
unsigned int options = 0,
Callback = Callback()) override;
@ -120,8 +120,8 @@ namespace musik { namespace core { namespace library {
std::string name;
std::thread* thread;
std::condition_variable queueCondition;
std::mutex mutex;
std::condition_variable_any queueCondition;
std::recursive_mutex mutex;
std::atomic<bool> exit;
core::IIndexer *indexer;

View File

@ -40,8 +40,10 @@
#include <core/library/query/local/AlbumListQuery.h>
#include <core/library/query/local/CategoryListQuery.h>
#include <core/library/query/local/CategoryTrackListQuery.h>
#include <core/library/query/local/DeletePlaylistQuery.h>
#include <core/library/query/local/SearchTrackListQuery.h>
#include <core/library/query/local/GetPlaylistQuery.h>
#include <core/library/query/local/SavePlaylistQuery.h>
#include <core/library/query/local/TrackMetadataQuery.h>
#include <core/library/track/LibraryTrack.h>
#include <core/library/track/RetainedTrack.h>
@ -209,4 +211,95 @@ IMetadataMapList* LocalSimpleDataProvider::QueryAlbums(
IMetadataMapList* LocalSimpleDataProvider::QueryAlbums(const char* filter) {
return this->QueryAlbums(nullptr, -1, filter);
}
uint64_t LocalSimpleDataProvider::SavePlaylist(
int64_t trackIds[],
size_t trackIdCount,
const char* name,
const uint64_t playlistId)
{
if (playlistId == 0 && (!name || !strlen(name))) {
return 0;
}
try {
std::shared_ptr<TrackList> sharedTrackList =
std::make_shared<TrackList>(this->library, trackIds, trackIdCount);
/* replacing (and optionally renaming) an existing playlist */
if (playlistId != 0) {
std::shared_ptr<SavePlaylistQuery> query =
SavePlaylistQuery::Replace(playlistId, sharedTrackList);
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
if (strlen(name)) {
query = SavePlaylistQuery::Rename(playlistId, name);
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
return playlistId;
}
}
else {
return playlistId;
}
}
}
else {
std::shared_ptr<SavePlaylistQuery> query =
SavePlaylistQuery::Save(name, sharedTrackList);
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
return query->GetPlaylistId();
}
}
}
catch (...) {
musik::debug::err(TAG, "SavePlaylist failed");
}
return 0;
}
bool LocalSimpleDataProvider::RenamePlaylist(const uint64_t playlistId, const char* name)
{
try {
std::shared_ptr<SavePlaylistQuery> query =
SavePlaylistQuery::Rename(playlistId, name);
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
return true;
}
}
catch (...) {
musik::debug::err(TAG, "RenamePlaylist failed");
}
return false;
}
bool LocalSimpleDataProvider::DeletePlaylist(const uint64_t playlistId) {
try {
std::shared_ptr<DeletePlaylistQuery> query =
std::make_shared<DeletePlaylistQuery>(playlistId);
this->library->Enqueue(query, ILibrary::QuerySynchronous);
if (query->GetStatus() == IQuery::Finished) {
return true;
}
}
catch (...) {
musik::debug::err(TAG, "DeletePlaylist failed");
}
return false;
}

View File

@ -49,11 +49,13 @@ namespace musik { namespace core { namespace db { namespace local {
QueryTracks(
const char* query = "",
int limit = -1,
int offset = 0);
int offset = 0) override;
virtual musik::core::sdk::IRetainedTrack* QueryTrackById(int64_t trackId);
virtual musik::core::sdk::IRetainedTrack*
QueryTrackById(int64_t trackId) override;
virtual musik::core::sdk::IRetainedTrack* QueryTrackByExternalId(const char* externalId);
virtual musik::core::sdk::IRetainedTrack*
QueryTrackByExternalId(const char* externalId) override;
virtual musik::core::sdk::ITrackList*
QueryTracksByCategory(
@ -61,20 +63,32 @@ namespace musik { namespace core { namespace db { namespace local {
int64_t selectedId,
const char* filter = "",
int limit = -1,
int offset = 0);
int offset = 0) override;
virtual musik::core::sdk::IMetadataValueList*
QueryCategory(
const char* type,
const char* filter = "");
const char* filter = "") override;
virtual musik::core::sdk::IMetadataMapList*
QueryAlbums(const char* filter = "");
QueryAlbums(const char* filter = "") override;
virtual musik::core::sdk::IMetadataMapList* QueryAlbums(
const char* categoryIdName,
int64_t categoryIdValue,
const char* filter = "");
const char* filter = "") override;
virtual uint64_t SavePlaylist(
int64_t trackIds[],
size_t trackIdCount,
const char* name,
const uint64_t playlistId = 0) override;
virtual bool RenamePlaylist(
const uint64_t playlistId,
const char* name) override;
virtual bool DeletePlaylist(const uint64_t playlistId) override;
private:
musik::core::ILibraryPtr library;

View File

@ -178,6 +178,10 @@ SavePlaylistQuery::SavePlaylistQuery(
SavePlaylistQuery::~SavePlaylistQuery() {
}
int64_t SavePlaylistQuery::GetPlaylistId() const {
return playlistId;
}
bool SavePlaylistQuery::AddTracksToPlaylist(
musik::core::db::Connection &db,
int64_t playlistId,
@ -245,17 +249,17 @@ bool SavePlaylistQuery::CreatePlaylist(musik::core::db::Connection &db) {
return false;
}
int64_t playlistId = db.LastInsertedId();
this->playlistId = db.LastInsertedId();
/* add tracks to playlist */
if (this->tracks) {
if (!this->AddTracksToPlaylist(db, playlistId, this->tracks)) {
if (!this->AddTracksToPlaylist(db, this->playlistId, this->tracks)) {
transaction.Cancel();
return false;
}
}
else {
if (!this->AddCategoryTracksToPlaylist(db, playlistId)) {
if (!this->AddCategoryTracksToPlaylist(db, this->playlistId)) {
transaction.Cancel();
return false;
}

View File

@ -76,6 +76,8 @@ namespace musik { namespace core { namespace db { namespace local {
virtual ~SavePlaylistQuery();
int64_t GetPlaylistId() const;
protected:
virtual bool OnRun(musik::core::db::Connection &db);

View File

@ -63,6 +63,11 @@ TrackList::TrackList(TrackList* other)
this->library = library;
}
TrackList::TrackList(ILibraryPtr library, int64_t trackIds[], size_t trackIdCount)
: library(library) {
this->ids.insert(this->ids.end(), &trackIds[0], &trackIds[trackIdCount]);
}
TrackList::~TrackList() {
}

View File

@ -62,6 +62,8 @@ namespace musik { namespace core {
public:
TrackList(ILibraryPtr library);
TrackList(TrackList* other);
TrackList(ILibraryPtr library, int64_t trackIds[], size_t trackIdCount);
virtual ~TrackList();
/* ITrackList */

View File

@ -69,6 +69,18 @@ namespace musik { namespace core { namespace sdk {
const char* categoryIdName,
int64_t categoryIdValue,
const char* filter = "") = 0;
virtual uint64_t SavePlaylist(
int64_t trackIds[],
size_t trackIdCount,
const char* playlistName,
const uint64_t playlistId = 0) = 0;
virtual bool RenamePlaylist(
const uint64_t playlistId,
const char* playlistName) = 0;
virtual bool DeletePlaylist(const uint64_t playlistId) = 0;
};
} } }

View File

@ -110,6 +110,8 @@ namespace key {
static const std::string relative = "relative";
static const std::string password = "password";
static const std::string authenticated = "authenticated";
static const std::string playlist_id = "playlist_id";
static const std::string playlist_name = "playlist_name";
}
namespace value {
@ -151,6 +153,9 @@ namespace request {
static const std::string play_tracks_by_category = "play_tracks_by_category";
static const std::string query_play_queue_tracks = "query_play_queue_tracks";
static const std::string get_environment = "get_environment";
static const std::string save_playlist = "save_playlist";
static const std::string rename_playlist = "rename_playlist";
static const std::string delete_playlist = "delete_playlist";
}
namespace fragment {

View File

@ -330,6 +330,18 @@ void WebSocketServer::HandleRequest(connection_hdl connection, json& request) {
this->RespondWithCurrentTime(connection, request);
return;
}
else if (name == request::save_playlist) {
this->RespondWithSavePlaylist(connection, request);
return;
}
else if (name == request::rename_playlist) {
this->RespondWithRenamePlaylist(connection, request);
return;
}
else if (name == request::delete_playlist) {
this->RespondWithDeletePlaylist(connection, request);
return;
}
}
this->RespondWithInvalidRequest(connection, name, id);
@ -770,6 +782,57 @@ void WebSocketServer::RespondWithCurrentTime(connection_hdl connection, json& re
});
}
void WebSocketServer::RespondWithSavePlaylist(connection_hdl connection, json& request) {
auto& options = request[message::options];
json& ids = options[key::ids];
if (ids.is_array()) {
int64_t id = options.value(key::playlist_id, 0);
std::string name = options.value(key::playlist_name, "");
size_t count = ids.size();
int64_t* idArray = new int64_t[count];
std::copy(ids.begin(), ids.end(), idArray);
uint64_t newPlaylistId = this->context.dataProvider
->SavePlaylist(idArray, count, name.c_str(), id);
delete[] idArray;
if (newPlaylistId != 0) {
this->RespondWithOptions(connection, request, {
{ key::playlist_id, newPlaylistId }
});
}
else {
this->RespondWithFailure(connection, request);
}
}
else {
this->RespondWithInvalidRequest(
connection, request[message::name], request[message::id]);
}
}
void WebSocketServer::RespondWithRenamePlaylist(connection_hdl connection, json& request) {
auto& options = request[message::options];
int64_t id = options[key::playlist_id];
std::string name = options[key::playlist_name];
this->context.dataProvider->RenamePlaylist(id, name.c_str())
? this->RespondWithSuccess(connection, request)
: this->RespondWithFailure(connection, request);
}
void WebSocketServer::RespondWithDeletePlaylist(connection_hdl connection, json& request) {
auto& options = request[message::options];
int64_t id = options[key::playlist_id];
this->context.dataProvider->DeletePlaylist(id)
? this->RespondWithSuccess(connection, request)
: this->RespondWithFailure(connection, request);
}
void WebSocketServer::BroadcastPlaybackOverview() {
{
auto rl = connectionLock.Read();

View File

@ -146,6 +146,9 @@ class WebSocketServer {
void RespondWithPlayTracksByCategory(connection_hdl connection, json& request);
void RespondWithEnvironment(connection_hdl connection, json& request);
void RespondWithCurrentTime(connection_hdl connection, json& request);
void RespondWithSavePlaylist(connection_hdl connection, json& request);
void RespondWithRenamePlaylist(connection_hdl connection, json& request);
void RespondWithDeletePlaylist(connection_hdl connection, json& request);
void BroadcastPlaybackOverview();
void BroadcastPlayQueueChanged();