Updated WebSocketServer to support creating new playlists from

subqueries, the same way appending playlists does.
This commit is contained in:
casey langen 2017-11-06 17:57:13 -08:00
parent d73178ed4e
commit 3f85bf00e3
8 changed files with 282 additions and 87 deletions

View File

@ -265,9 +265,10 @@ IMapList* LocalSimpleDataProvider::QueryAlbums(const char* filter) {
return this->QueryAlbums(nullptr, -1, filter);
}
template <typename TrackListType>
static uint64_t savePlaylist(
ILibraryPtr library,
std::shared_ptr<TrackList> 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<SavePlaylistQuery> 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<SavePlaylistQuery> 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<SavePlaylistQuery> query =
SavePlaylistQuery::Rename(playlistId, name);
if (strlen(name)) {
try {
std::shared_ptr<SavePlaylistQuery> 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;

View File

@ -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;

View File

@ -36,6 +36,8 @@
#include "SavePlaylistQuery.h"
#include "CategoryTrackListQuery.h"
#include <core/library/track/LibraryTrack.h>
#include <core/library/query/local/TrackMetadataQuery.h>
#include <core/db/ScopedTransaction.h>
#include <core/db/Statement.h>
@ -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> SavePlaylistQuery::Save(
musik::core::ILibraryPtr library,
const std::string& playlistName,
std::shared_ptr<musik::core::TrackList> tracks)
{
return std::shared_ptr<SavePlaylistQuery>(
new SavePlaylistQuery(playlistName, tracks));
new SavePlaylistQuery(library, playlistName, tracks));
}
std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Save(
musik::core::ILibraryPtr library,
const std::string& playlistName,
musik::core::sdk::ITrackList* tracks)
{
return std::shared_ptr<SavePlaylistQuery>(
new SavePlaylistQuery(library, playlistName, tracks));
}
std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Save(
@ -77,11 +93,21 @@ std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Save(
}
std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Replace(
musik::core::ILibraryPtr library,
const int64_t playlistId,
std::shared_ptr<musik::core::TrackList> tracks)
{
return std::shared_ptr<SavePlaylistQuery>(
new SavePlaylistQuery(playlistId, tracks));
new SavePlaylistQuery(library, playlistId, tracks));
}
std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Replace(
musik::core::ILibraryPtr library,
const int64_t playlistId,
musik::core::sdk::ITrackList* tracks)
{
return std::shared_ptr<SavePlaylistQuery>(
new SavePlaylistQuery(library, playlistId, tracks));
}
std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Rename(
@ -92,16 +118,20 @@ std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Rename(
}
std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Append(
const int64_t playlistId, std::shared_ptr<musik::core::TrackList> tracks)
musik::core::ILibraryPtr library,
const int64_t playlistId,
std::shared_ptr<musik::core::TrackList> tracks)
{
auto result = std::shared_ptr<SavePlaylistQuery>(
new SavePlaylistQuery(playlistId, tracks));
new SavePlaylistQuery(library, playlistId, tracks));
result->op = AppendOp;
return result;
}
/* CONSTRUCTORS */
std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Append(
musik::core::ILibraryPtr library,
const int64_t playlistId,
@ -117,16 +147,31 @@ std::shared_ptr<SavePlaylistQuery> SavePlaylistQuery::Append(
}
SavePlaylistQuery::SavePlaylistQuery(
musik::core::ILibraryPtr library,
const std::string& playlistName,
std::shared_ptr<musik::core::TrackList> 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<musik::core::TrackList> 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<musik::core::TrackList> 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<musik::core::TrackList> 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<LibraryTrack>(rawTracks->GetId(index), library);
if (rawTracks) {
std::shared_ptr<TrackMetadataQuery> query(
new TrackMetadataQuery(result, library, TrackMetadataQuery::IdsOnly));
library->Enqueue(query, ILibrary::QuerySynchronous);
}
return result;
}

View File

@ -45,9 +45,15 @@ namespace musik { namespace core { namespace db { namespace local {
class SavePlaylistQuery : public musik::core::db::LocalQueryBase {
public:
static std::shared_ptr<SavePlaylistQuery> Save(
musik::core::ILibraryPtr library,
const std::string& playlistName,
std::shared_ptr<musik::core::TrackList> tracks);
static std::shared_ptr<SavePlaylistQuery> Save(
musik::core::ILibraryPtr library,
const std::string& playlistName,
musik::core::sdk::ITrackList* tracks);
static std::shared_ptr<SavePlaylistQuery> 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<SavePlaylistQuery> Replace(
musik::core::ILibraryPtr library,
const int64_t playlistId,
std::shared_ptr<musik::core::TrackList> tracks);
static std::shared_ptr<SavePlaylistQuery> Replace(
musik::core::ILibraryPtr library,
const int64_t playlistId,
musik::core::sdk::ITrackList* tracks);
static std::shared_ptr<SavePlaylistQuery> Rename(
const int64_t playlistId,
const std::string& playlistName);
static std::shared_ptr<SavePlaylistQuery> Append(
musik::core::ILibraryPtr library,
const int64_t playlistId,
std::shared_ptr<musik::core::TrackList> 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<musik::core::TrackList> 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<musik::core::TrackList> 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<musik::core::TrackList> shared);
bool Exists();
size_t Count();
TrackPtr Get(musik::core::ILibraryPtr library, size_t index);
std::shared_ptr<musik::core::TrackList> 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<musik::core::TrackList> tracks);
TrackListWrapper& tracks);
Operation op;
musik::core::ILibraryPtr library;
std::string playlistName, categoryType;
int64_t playlistId, categoryId;
std::shared_ptr<musik::core::TrackList> tracks;
TrackListWrapper tracks;
};
} } } }

View File

@ -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;

View File

@ -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<TrackList>(new TrackList(tracks))));
return true;

View File

@ -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));

View File

@ -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);