diff --git a/src/core/library/LocalLibrary.cpp b/src/core/library/LocalLibrary.cpp index ee4235ddb..1edf14425 100644 --- a/src/core/library/LocalLibrary.cpp +++ b/src/core/library/LocalLibrary.cpp @@ -114,7 +114,7 @@ void LocalLibrary::Close() { std::thread* thread = nullptr; { - std::unique_lock lock(this->mutex); + std::unique_lock 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(query); if (localQuery) { - std::unique_lock lock(this->mutex); + std::unique_lock 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 lock(this->mutex); + std::unique_lock lock(this->mutex); while (!this->queryQueue.size() && !this->exit) { this->queueCondition.wait(lock); } diff --git a/src/core/library/LocalLibrary.h b/src/core/library/LocalLibrary.h index f3f45fc59..12cfc0f0c 100644 --- a/src/core/library/LocalLibrary.h +++ b/src/core/library/LocalLibrary.h @@ -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 exit; core::IIndexer *indexer; diff --git a/src/core/library/LocalSimpleDataProvider.cpp b/src/core/library/LocalSimpleDataProvider.cpp index 608000dd0..dbbcb7600 100644 --- a/src/core/library/LocalSimpleDataProvider.cpp +++ b/src/core/library/LocalSimpleDataProvider.cpp @@ -40,8 +40,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -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 sharedTrackList = + std::make_shared(this->library, trackIds, trackIdCount); + + /* replacing (and optionally renaming) an existing playlist */ + if (playlistId != 0) { + std::shared_ptr 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 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 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 query = + std::make_shared(playlistId); + + this->library->Enqueue(query, ILibrary::QuerySynchronous); + + if (query->GetStatus() == IQuery::Finished) { + return true; + } + } + catch (...) { + musik::debug::err(TAG, "DeletePlaylist failed"); + } + + return false; } \ No newline at end of file diff --git a/src/core/library/LocalSimpleDataProvider.h b/src/core/library/LocalSimpleDataProvider.h index fe4fcfd93..cfefcdead 100644 --- a/src/core/library/LocalSimpleDataProvider.h +++ b/src/core/library/LocalSimpleDataProvider.h @@ -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; diff --git a/src/core/library/query/local/SavePlaylistQuery.cpp b/src/core/library/query/local/SavePlaylistQuery.cpp index 463fef88a..7d2428bf4 100644 --- a/src/core/library/query/local/SavePlaylistQuery.cpp +++ b/src/core/library/query/local/SavePlaylistQuery.cpp @@ -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; } diff --git a/src/core/library/query/local/SavePlaylistQuery.h b/src/core/library/query/local/SavePlaylistQuery.h index b080bf70b..9cad207a1 100644 --- a/src/core/library/query/local/SavePlaylistQuery.h +++ b/src/core/library/query/local/SavePlaylistQuery.h @@ -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); diff --git a/src/core/library/track/TrackList.cpp b/src/core/library/track/TrackList.cpp index 9b1c53665..d2df28ac2 100755 --- a/src/core/library/track/TrackList.cpp +++ b/src/core/library/track/TrackList.cpp @@ -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() { } diff --git a/src/core/library/track/TrackList.h b/src/core/library/track/TrackList.h index 53203b394..4fe6e7b90 100755 --- a/src/core/library/track/TrackList.h +++ b/src/core/library/track/TrackList.h @@ -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 */ diff --git a/src/core/sdk/ISimpleDataProvider.h b/src/core/sdk/ISimpleDataProvider.h index 40762d6c7..26df4f43f 100644 --- a/src/core/sdk/ISimpleDataProvider.h +++ b/src/core/sdk/ISimpleDataProvider.h @@ -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; }; } } } diff --git a/src/plugins/websocket_remote/Constants.h b/src/plugins/websocket_remote/Constants.h index 5dd3c871f..2283fa9c6 100644 --- a/src/plugins/websocket_remote/Constants.h +++ b/src/plugins/websocket_remote/Constants.h @@ -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 { diff --git a/src/plugins/websocket_remote/WebSocketServer.cpp b/src/plugins/websocket_remote/WebSocketServer.cpp index bdbf2fc84..01ff6f018 100644 --- a/src/plugins/websocket_remote/WebSocketServer.cpp +++ b/src/plugins/websocket_remote/WebSocketServer.cpp @@ -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(); diff --git a/src/plugins/websocket_remote/WebSocketServer.h b/src/plugins/websocket_remote/WebSocketServer.h index 43a52caf7..3688a9ac5 100644 --- a/src/plugins/websocket_remote/WebSocketServer.h +++ b/src/plugins/websocket_remote/WebSocketServer.h @@ -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();