From 3f85bf00e30cce805594ae13c406449d28ac039e Mon Sep 17 00:00:00 2001 From: casey langen Date: Mon, 6 Nov 2017 17:57:13 -0800 Subject: [PATCH] Updated WebSocketServer to support creating new playlists from subqueries, the same way appending playlists does. --- src/core/library/LocalSimpleDataProvider.cpp | 39 +++-- src/core/library/LocalSimpleDataProvider.h | 5 + .../library/query/local/SavePlaylistQuery.cpp | 128 ++++++++++++++-- .../library/query/local/SavePlaylistQuery.h | 41 ++++- src/core/sdk/ISimpleDataProvider.h | 5 + src/musikcube/app/layout/BrowseLayout.cpp | 1 + .../app/overlay/PlayQueueOverlays.cpp | 6 +- .../websocket_remote/WebSocketServer.cpp | 144 +++++++++++------- 8 files changed, 282 insertions(+), 87 deletions(-) diff --git a/src/core/library/LocalSimpleDataProvider.cpp b/src/core/library/LocalSimpleDataProvider.cpp index 7bfb672f9..0f679d0f4 100644 --- a/src/core/library/LocalSimpleDataProvider.cpp +++ b/src/core/library/LocalSimpleDataProvider.cpp @@ -265,9 +265,10 @@ IMapList* LocalSimpleDataProvider::QueryAlbums(const char* filter) { return this->QueryAlbums(nullptr, -1, filter); } +template static uint64_t savePlaylist( ILibraryPtr library, - std::shared_ptr trackList, + TrackListType trackList, const char* playlistName, const int64_t playlistId) { @@ -275,7 +276,7 @@ static uint64_t savePlaylist( /* replacing (and optionally renaming) an existing playlist */ if (playlistId != 0) { std::shared_ptr query = - SavePlaylistQuery::Replace(playlistId, trackList); + SavePlaylistQuery::Replace(library, playlistId, trackList); library->Enqueue(query, ILibrary::QuerySynchronous); @@ -296,7 +297,7 @@ static uint64_t savePlaylist( } else { std::shared_ptr query = - SavePlaylistQuery::Save(playlistName, trackList); + SavePlaylistQuery::Save(library, playlistName, trackList); library->Enqueue(query, ILibrary::QuerySynchronous); @@ -352,20 +353,34 @@ int64_t LocalSimpleDataProvider::SavePlaylistWithExternalIds( return 0; } +int64_t LocalSimpleDataProvider::SavePlaylistWithTrackList( + ITrackList* trackList, + const char* playlistName, + const int64_t playlistId) +{ + if (playlistId == 0 && (!playlistName || !strlen(playlistName))) { + return 0; + } + + return savePlaylist(this->library, trackList, playlistName, playlistId); +} + bool LocalSimpleDataProvider::RenamePlaylist(const int64_t playlistId, const char* name) { - try { - std::shared_ptr query = - SavePlaylistQuery::Rename(playlistId, name); + if (strlen(name)) { + try { + std::shared_ptr query = + SavePlaylistQuery::Rename(playlistId, name); - this->library->Enqueue(query, ILibrary::QuerySynchronous); + this->library->Enqueue(query, ILibrary::QuerySynchronous); - if (query->GetStatus() == IQuery::Finished) { - return true; + if (query->GetStatus() == IQuery::Finished) { + return true; + } + } + catch (...) { + musik::debug::err(TAG, "RenamePlaylist failed"); } - } - catch (...) { - musik::debug::err(TAG, "RenamePlaylist failed"); } return false; diff --git a/src/core/library/LocalSimpleDataProvider.h b/src/core/library/LocalSimpleDataProvider.h index c1316cb44..5173c0a31 100644 --- a/src/core/library/LocalSimpleDataProvider.h +++ b/src/core/library/LocalSimpleDataProvider.h @@ -90,6 +90,11 @@ namespace musik { namespace core { namespace db { namespace local { const char* playlistName, const int64_t playlistId = 0) override; + virtual int64_t SavePlaylistWithTrackList( + musik::core::sdk::ITrackList* trackList, + const char* playlistName, + const int64_t playlistId = 0) override; + virtual bool RenamePlaylist( const int64_t playlistId, const char* name) override; diff --git a/src/core/library/query/local/SavePlaylistQuery.cpp b/src/core/library/query/local/SavePlaylistQuery.cpp index 2753d5052..9abcb5d9c 100644 --- a/src/core/library/query/local/SavePlaylistQuery.cpp +++ b/src/core/library/query/local/SavePlaylistQuery.cpp @@ -36,6 +36,8 @@ #include "SavePlaylistQuery.h" #include "CategoryTrackListQuery.h" +#include +#include #include #include @@ -43,6 +45,8 @@ using namespace musik::core; using namespace musik::core::db; using namespace musik::core::db::local; +/* CONSTANTS */ + static std::string CREATE_PLAYLIST_QUERY = "INSERT INTO playlists (name) VALUES (?);"; @@ -58,12 +62,24 @@ static std::string RENAME_PLAYLIST_QUERY = static std::string GET_MAX_SORT_ORDER_QUERY = "SELECT MAX(sort_order) from playlist_tracks where playlist_id = ?"; +/* STATIC FACTORY METHODS */ + std::shared_ptr SavePlaylistQuery::Save( + musik::core::ILibraryPtr library, const std::string& playlistName, std::shared_ptr tracks) { return std::shared_ptr( - new SavePlaylistQuery(playlistName, tracks)); + new SavePlaylistQuery(library, playlistName, tracks)); +} + +std::shared_ptr SavePlaylistQuery::Save( + musik::core::ILibraryPtr library, + const std::string& playlistName, + musik::core::sdk::ITrackList* tracks) +{ + return std::shared_ptr( + new SavePlaylistQuery(library, playlistName, tracks)); } std::shared_ptr SavePlaylistQuery::Save( @@ -77,11 +93,21 @@ std::shared_ptr SavePlaylistQuery::Save( } std::shared_ptr SavePlaylistQuery::Replace( + musik::core::ILibraryPtr library, const int64_t playlistId, std::shared_ptr tracks) { return std::shared_ptr( - new SavePlaylistQuery(playlistId, tracks)); + new SavePlaylistQuery(library, playlistId, tracks)); +} + +std::shared_ptr SavePlaylistQuery::Replace( + musik::core::ILibraryPtr library, + const int64_t playlistId, + musik::core::sdk::ITrackList* tracks) +{ + return std::shared_ptr( + new SavePlaylistQuery(library, playlistId, tracks)); } std::shared_ptr SavePlaylistQuery::Rename( @@ -92,16 +118,20 @@ std::shared_ptr SavePlaylistQuery::Rename( } std::shared_ptr SavePlaylistQuery::Append( - const int64_t playlistId, std::shared_ptr tracks) + musik::core::ILibraryPtr library, + const int64_t playlistId, + std::shared_ptr tracks) { auto result = std::shared_ptr( - new SavePlaylistQuery(playlistId, tracks)); + new SavePlaylistQuery(library, playlistId, tracks)); result->op = AppendOp; return result; } +/* CONSTRUCTORS */ + std::shared_ptr SavePlaylistQuery::Append( musik::core::ILibraryPtr library, const int64_t playlistId, @@ -117,16 +147,31 @@ std::shared_ptr SavePlaylistQuery::Append( } SavePlaylistQuery::SavePlaylistQuery( + musik::core::ILibraryPtr library, const std::string& playlistName, std::shared_ptr tracks) { + this->library = library; this->playlistId = -1; this->categoryId = -1; this->playlistName = playlistName; - this->tracks = tracks; + this->tracks.rawTracks = nullptr; + this->tracks.sharedTracks = tracks; this->op = CreateOp; } +SavePlaylistQuery::SavePlaylistQuery( + musik::core::ILibraryPtr library, + const std::string& playlistName, + musik::core::sdk::ITrackList* tracks) +{ + this->library = library; + this->playlistId = -1; + this->categoryId = -1; + this->playlistName = playlistName; + this->tracks.rawTracks = tracks; + this->op = CreateOp; +} SavePlaylistQuery::SavePlaylistQuery( musik::core::ILibraryPtr library, @@ -143,11 +188,24 @@ SavePlaylistQuery::SavePlaylistQuery( } SavePlaylistQuery::SavePlaylistQuery( + musik::core::ILibraryPtr library, const int64_t playlistId, std::shared_ptr tracks) { + this->library = library; this->playlistId = playlistId; - this->tracks = tracks; + this->tracks.sharedTracks = tracks; + this->op = ReplaceOp; +} + +SavePlaylistQuery::SavePlaylistQuery( + musik::core::ILibraryPtr library, + const int64_t playlistId, + musik::core::sdk::ITrackList* tracks) +{ + this->library = library; + this->playlistId = playlistId; + this->tracks.rawTracks = tracks; this->op = ReplaceOp; } @@ -161,7 +219,6 @@ SavePlaylistQuery::SavePlaylistQuery( this->playlistId = playlistId; this->categoryId = categoryId; this->categoryType = categoryType; - this->tracks = tracks; this->op = AppendOp; } @@ -178,6 +235,8 @@ SavePlaylistQuery::SavePlaylistQuery( SavePlaylistQuery::~SavePlaylistQuery() { } +/* METHODS */ + int64_t SavePlaylistQuery::GetPlaylistId() const { return playlistId; } @@ -185,7 +244,7 @@ int64_t SavePlaylistQuery::GetPlaylistId() const { bool SavePlaylistQuery::AddTracksToPlaylist( musik::core::db::Connection &db, int64_t playlistId, - std::shared_ptr tracks) + TrackListWrapper& tracks) { int offset = 0; @@ -201,8 +260,8 @@ bool SavePlaylistQuery::AddTracksToPlaylist( Statement insertTrack(INSERT_PLAYLIST_TRACK_QUERY.c_str(), db); TrackPtr track; - for (size_t i = 0; i < tracks->Count(); i++) { - track = tracks->Get(i); + for (size_t i = 0; i < tracks.Count(); i++) { + track = tracks.Get(this->library, i); if (track) { insertTrack.Reset(); insertTrack.BindText(0, track->GetString("external_id")); @@ -229,7 +288,7 @@ bool SavePlaylistQuery::AddCategoryTracksToPlaylist( if (query->GetStatus() == IQuery::Finished) { auto tracks = query->GetResult(); - if (this->AddTracksToPlaylist(db, playlistId, tracks)) { + if (this->AddTracksToPlaylist(db, playlistId, TrackListWrapper(tracks))) { return true; } } @@ -252,7 +311,7 @@ bool SavePlaylistQuery::CreatePlaylist(musik::core::db::Connection &db) { this->playlistId = db.LastInsertedId(); /* add tracks to playlist */ - if (this->tracks) { + if (this->tracks.Exists()) { if (!this->AddTracksToPlaylist(db, this->playlistId, this->tracks)) { transaction.Cancel(); return false; @@ -299,7 +358,7 @@ bool SavePlaylistQuery::ReplacePlaylist(musik::core::db::Connection &db) { bool SavePlaylistQuery::AppendToPlaylist(musik::core::db::Connection& db) { ScopedTransaction transaction(db); - bool result = this->tracks + bool result = this->tracks.Exists() ? this->AddTracksToPlaylist(db, this->playlistId, this->tracks) : this->AddCategoryTracksToPlaylist(db, this->playlistId); @@ -319,3 +378,46 @@ bool SavePlaylistQuery::OnRun(musik::core::db::Connection &db) { } return false; } + +/* SUPPORTING TYPES */ + +SavePlaylistQuery::TrackListWrapper::TrackListWrapper() { + this->rawTracks = nullptr; +} + +SavePlaylistQuery::TrackListWrapper::TrackListWrapper( + std::shared_ptr shared) +{ + this->rawTracks = nullptr; + this->sharedTracks = shared; +} + +bool SavePlaylistQuery::TrackListWrapper::Exists() { + return this->sharedTracks || this->rawTracks; +} + +size_t SavePlaylistQuery::TrackListWrapper::Count() { + if (sharedTracks) { + return sharedTracks->Count(); + } + + return rawTracks ? rawTracks->Count() : 0; +} + +TrackPtr SavePlaylistQuery::TrackListWrapper::Get( + musik::core::ILibraryPtr library, size_t index) +{ + if (sharedTracks) { + return sharedTracks->Get(index); + } + + TrackPtr result = std::make_shared(rawTracks->GetId(index), library); + if (rawTracks) { + std::shared_ptr query( + new TrackMetadataQuery(result, library, TrackMetadataQuery::IdsOnly)); + + library->Enqueue(query, ILibrary::QuerySynchronous); + } + + return result; +} \ No newline at end of file diff --git a/src/core/library/query/local/SavePlaylistQuery.h b/src/core/library/query/local/SavePlaylistQuery.h index 9cad207a1..b5c7436fc 100644 --- a/src/core/library/query/local/SavePlaylistQuery.h +++ b/src/core/library/query/local/SavePlaylistQuery.h @@ -45,9 +45,15 @@ namespace musik { namespace core { namespace db { namespace local { class SavePlaylistQuery : public musik::core::db::LocalQueryBase { public: static std::shared_ptr Save( + musik::core::ILibraryPtr library, const std::string& playlistName, std::shared_ptr tracks); + static std::shared_ptr Save( + musik::core::ILibraryPtr library, + const std::string& playlistName, + musik::core::sdk::ITrackList* tracks); + static std::shared_ptr Save( musik::core::ILibraryPtr library, const std::string& playlistName, @@ -55,14 +61,21 @@ namespace musik { namespace core { namespace db { namespace local { int64_t categoryId); static std::shared_ptr Replace( + musik::core::ILibraryPtr library, const int64_t playlistId, std::shared_ptr tracks); + static std::shared_ptr Replace( + musik::core::ILibraryPtr library, + const int64_t playlistId, + musik::core::sdk::ITrackList* tracks); + static std::shared_ptr Rename( const int64_t playlistId, const std::string& playlistName); static std::shared_ptr Append( + musik::core::ILibraryPtr library, const int64_t playlistId, std::shared_ptr tracks); @@ -83,9 +96,15 @@ namespace musik { namespace core { namespace db { namespace local { private: SavePlaylistQuery( + musik::core::ILibraryPtr library, const std::string& playlistName, std::shared_ptr tracks); + SavePlaylistQuery( + musik::core::ILibraryPtr library, + const std::string& playlistName, + musik::core::sdk::ITrackList* tracks); + SavePlaylistQuery( musik::core::ILibraryPtr library, const std::string& playlistName, @@ -93,9 +112,15 @@ namespace musik { namespace core { namespace db { namespace local { int64_t categoryId); SavePlaylistQuery( + musik::core::ILibraryPtr library, const int64_t playlistId, std::shared_ptr tracks); + SavePlaylistQuery( + musik::core::ILibraryPtr library, + const int64_t playlistId, + musik::core::sdk::ITrackList* tracks); + SavePlaylistQuery( musik::core::ILibraryPtr library, const int64_t playlistId, @@ -106,6 +131,18 @@ namespace musik { namespace core { namespace db { namespace local { const int64_t playlistId, const std::string& newName); + struct TrackListWrapper { + TrackListWrapper(); + TrackListWrapper(std::shared_ptr shared); + + bool Exists(); + size_t Count(); + TrackPtr Get(musik::core::ILibraryPtr library, size_t index); + + std::shared_ptr sharedTracks; + musik::core::sdk::ITrackList* rawTracks; + }; + enum Operation { CreateOp, RenameOp, ReplaceOp, AppendOp }; bool CreatePlaylist(musik::core::db::Connection &db); @@ -118,13 +155,13 @@ namespace musik { namespace core { namespace db { namespace local { bool AddTracksToPlaylist( musik::core::db::Connection &db, int64_t playlistId, - std::shared_ptr tracks); + TrackListWrapper& tracks); Operation op; musik::core::ILibraryPtr library; std::string playlistName, categoryType; int64_t playlistId, categoryId; - std::shared_ptr tracks; + TrackListWrapper tracks; }; } } } } diff --git a/src/core/sdk/ISimpleDataProvider.h b/src/core/sdk/ISimpleDataProvider.h index 3f46efdb4..c05523076 100644 --- a/src/core/sdk/ISimpleDataProvider.h +++ b/src/core/sdk/ISimpleDataProvider.h @@ -82,6 +82,11 @@ namespace musik { namespace core { namespace sdk { const char* playlistName, const int64_t playlistId = 0) = 0; + virtual int64_t SavePlaylistWithTrackList( + ITrackList* trackList, + const char* playlistName, + const int64_t playlistId = 0) = 0; + virtual bool RenamePlaylist( const int64_t playlistId, const char* playlistName) = 0; diff --git a/src/musikcube/app/layout/BrowseLayout.cpp b/src/musikcube/app/layout/BrowseLayout.cpp index 9ac40b8b9..e02607053 100755 --- a/src/musikcube/app/layout/BrowseLayout.cpp +++ b/src/musikcube/app/layout/BrowseLayout.cpp @@ -300,6 +300,7 @@ bool BrowseLayout::ProcessPlaylistOperation(const std::string& key) { this->ShowModifiedLabel(false); auto tracks = this->trackList->GetTrackList().get(); this->library->Enqueue(SavePlaylistQuery::Replace( + this->library, this->categoryList->GetSelectedId(), std::shared_ptr(new TrackList(tracks)))); return true; diff --git a/src/musikcube/app/overlay/PlayQueueOverlays.cpp b/src/musikcube/app/overlay/PlayQueueOverlays.cpp index 375cab526..a9d426f84 100644 --- a/src/musikcube/app/overlay/PlayQueueOverlays.cpp +++ b/src/musikcube/app/overlay/PlayQueueOverlays.cpp @@ -185,7 +185,7 @@ static void confirmOverwritePlaylist( "ENTER", _TSTR("button_yes"), [library, playlistId, tracks](const std::string& str) { - library->Enqueue(SavePlaylistQuery::Replace(playlistId, tracks)); + library->Enqueue(SavePlaylistQuery::Replace(library, playlistId, tracks)); }); App::Overlays().Push(dialog); @@ -205,7 +205,7 @@ static void createNewPlaylist( .SetInputAcceptedCallback( [&queue, tracks, library, callback](const std::string& name) { if (name.size()) { - auto query = SavePlaylistQuery::Save(name, tracks); + auto query = SavePlaylistQuery::Save(library, name, tracks); library->Enqueue(query, 0, [&queue, callback](auto query) { queue.Post(Message::Create(nullptr, message::PlaylistCreated)); @@ -444,7 +444,7 @@ static void showAddTrackToPlaylistOverlay( int64_t playlistId = (*result)[index - 1]->id; setLastPlaylistId(playlistId); - library->Enqueue(SavePlaylistQuery::Append(playlistId, list), 0, + library->Enqueue(SavePlaylistQuery::Append(library, playlistId, list), 0, [&queue, playlistId](auto query) { /* the nesting is real... */ queue.Post(Message::Create(nullptr, message::TracksAddedToPlaylist, playlistId)); diff --git a/src/plugins/websocket_remote/WebSocketServer.cpp b/src/plugins/websocket_remote/WebSocketServer.cpp index 36023f5ea..d83e93853 100644 --- a/src/plugins/websocket_remote/WebSocketServer.cpp +++ b/src/plugins/websocket_remote/WebSocketServer.cpp @@ -790,82 +790,110 @@ void WebSocketServer::RespondWithCurrentTime(connection_hdl connection, json& re } void WebSocketServer::RespondWithSavePlaylist(connection_hdl connection, json& request) { + /* TODO: a lot of copy/paste between this method and RespondWithAppendToPlaylist */ + auto& options = request[message::options]; int64_t id = options.value(key::playlist_id, 0); + std::string name = options.value(key::playlist_name, ""); - if (id) { - /* by int64 id (faster, but less reliable) */ - if (options.find(key::ids) != options.end()) { - json& ids = options[key::ids]; + /* by int64 id (faster, but less reliable) */ + if (options.find(key::ids) != options.end()) { + json& ids = options[key::ids]; - if (ids.is_array()) { - std::string name = options.value(key::playlist_name, ""); + if (ids.is_array()) { + size_t count = ids.size(); + int64_t* idArray = new int64_t[count]; - size_t count = ids.size(); - int64_t* idArray = new int64_t[count]; - - if (count > 0) { - std::copy(ids.begin(), ids.end(), idArray); - } - - int64_t newPlaylistId = this->context.dataProvider - ->SavePlaylistWithIds(idArray, count, name.c_str(), id); - - delete[] idArray; - - if (newPlaylistId != 0) { - this->RespondWithOptions(connection, request, { - { key::playlist_id, newPlaylistId } - }); - return; - } - else { - this->RespondWithFailure(connection, request); - return; - } + if (count > 0) { + std::copy(ids.begin(), ids.end(), idArray); } + + int64_t newPlaylistId = this->context.dataProvider + ->SavePlaylistWithIds(idArray, count, name.c_str(), id); + + delete[] idArray; + + if (newPlaylistId != 0) { + this->RespondWithOptions(connection, request, { + { key::playlist_id, newPlaylistId } + }); + return; + } + + this->RespondWithFailure(connection, request); + return; } - /* by external id (slower, more reliable) */ - else if (options.find(key::external_ids) != options.end()) { - json& externalIds = options[key::external_ids]; + } + /* by external id (slower, more reliable) */ + else if (options.find(key::external_ids) != options.end()) { + json& externalIds = options[key::external_ids]; - if (externalIds.is_array()) { - std::string name = options.value(key::playlist_name, ""); + if (externalIds.is_array()) { + size_t count = externalIds.size(); + char** externalIdArray = (char**)malloc(count * sizeof(char*)); - size_t count = externalIds.size(); - char** externalIdArray = (char**)malloc(count * sizeof(char*)); + for (size_t i = 0; i < count; i++) { + std::string externalId = externalIds[i]; + size_t size = externalId.size(); + externalIdArray[i] = (char*)malloc(size + 1); + strncpy(externalIdArray[i], externalId.c_str(), size); + externalIdArray[i][size] = 0; + } - for (size_t i = 0; i < count; i++) { - std::string externalId = externalIds[i]; - size_t size = externalId.size(); - externalIdArray[i] = (char*)malloc(size + 1); - strncpy(externalIdArray[i], externalId.c_str(), size); - externalIdArray[i][size] = 0; - } + int64_t newPlaylistId = this->context.dataProvider + ->SavePlaylistWithExternalIds( + (const char**)externalIdArray, + count, + name.c_str(), + id); + for (size_t i = 0; i < count; i++) { + free(externalIdArray[i]); + } + + free(externalIdArray); + + if (newPlaylistId != 0) { + this->RespondWithOptions(connection, request, { + { key::playlist_id, newPlaylistId } + }); + return; + } + + this->RespondWithFailure(connection, request); + return; + } + } + /* by subquery (query_tracks or query_tracks_by_category) */ + else if (options.find(key::subquery) != options.end()) { + auto& subquery = options[key::subquery]; + std::string type = subquery.value(key::type, ""); + + if (subquery.find(message::options) != subquery.end()) { + ITrackList* tracks = nullptr; + int queryLimit, queryOffset; + if (type == request::query_tracks) { + tracks = this->QueryTracks(subquery, queryLimit, queryOffset); + } + else if (type == request::query_tracks_by_category) { + tracks = this->QueryTracksByCategory(subquery, queryLimit, queryOffset); + } + + if (tracks) { int64_t newPlaylistId = this->context.dataProvider - ->SavePlaylistWithExternalIds( - (const char**)externalIdArray, - count, - name.c_str(), - id); + ->SavePlaylistWithTrackList(tracks, name.c_str(), id); - for (size_t i = 0; i < count; i++) { - free(externalIdArray[i]); - } + tracks->Release(); - free(externalIdArray); - - if (newPlaylistId != 0) { + if (newPlaylistId > 0) { this->RespondWithOptions(connection, request, { { key::playlist_id, newPlaylistId } }); return; } - else { - this->RespondWithFailure(connection, request); - return; - } + + this->RespondWithFailure(connection, request); + return; } } } @@ -894,6 +922,8 @@ void WebSocketServer::RespondWithDeletePlaylist(connection_hdl connection, json& } void WebSocketServer::RespondWithAppendToPlaylist(connection_hdl connection, json& request) { + /* TODO: a lot of copy/paste between this method and RespondWithSavePlaylist */ + auto& options = request[message::options]; int offset = options.value(key::offset, -1); int64_t id = options.value(key::playlist_id, 0);