diff --git a/ext/clementine-spotifyblob/spotifyclient.cpp b/ext/clementine-spotifyblob/spotifyclient.cpp index 7d991048e..87e6bc660 100644 --- a/ext/clementine-spotifyblob/spotifyclient.cpp +++ b/ext/clementine-spotifyblob/spotifyclient.cpp @@ -372,6 +372,7 @@ void SpotifyClient::PlaylistContainerLoadedCallback(sp_playlistcontainer* pc, SpotifyClient* me = reinterpret_cast(userdata); // Install callbacks on all the playlists + sp_playlist_add_callbacks(sp_session_starred_create(me->session_), &me->get_playlists_callbacks_, me); const int count = sp_playlistcontainer_num_playlists(pc); for (int i = 0; i < count; ++i) { sp_playlist* playlist = sp_playlistcontainer_playlist(pc, i); @@ -611,11 +612,10 @@ void SpotifyClient::PlaylistStateChangedForGetPlaylists(sp_playlist* pl, void SpotifyClient::AddTracksToPlaylist( const pb::spotify::AddTracksToPlaylistRequest& req) { // Get the playlist we want to update - int playlist_index = req.playlist_index(); - sp_playlist* playlist = - GetPlaylist(pb::spotify::UserPlaylist, playlist_index); + sp_playlist* playlist = GetPlaylist(req.playlist_type(), req.playlist_index()); if (!playlist) { - qLog(Error) << "Playlist " << playlist_index << "not found"; + qLog(Error) << "Playlist " << req.playlist_type() << "," << + req.playlist_index() << "not found"; return; } @@ -650,11 +650,11 @@ void SpotifyClient::AddTracksToPlaylist( void SpotifyClient::RemoveTracksFromPlaylist( const pb::spotify::RemoveTracksFromPlaylistRequest& req) { // Get the playlist we want to update - int playlist_index = req.playlist_index(); sp_playlist* playlist = - GetPlaylist(pb::spotify::UserPlaylist, playlist_index); + GetPlaylist(req.playlist_type(), req.playlist_index()); if (!playlist) { - qLog(Error) << "Playlist " << playlist_index << "not found"; + qLog(Error) << "Playlist " << req.playlist_type() << "," << + req.playlist_index() << "not found"; return; } @@ -664,6 +664,15 @@ void SpotifyClient::RemoveTracksFromPlaylist( tracks_indices_array[i] = req.track_index(i); } + // WTF: sp_playlist_remove_tracks indexes start from the end for starred + // playlist, not from the beginning like other playlists: reverse them + if (req.playlist_type() == pb::spotify::Starred) { + int num_tracks = sp_playlist_num_tracks(playlist); + for (int i = 0; i < req.track_index_size(); i++) { + tracks_indices_array[i] = num_tracks - tracks_indices_array[i] - 1; + } + } + if (sp_playlist_remove_tracks(playlist, tracks_indices_array.get(), req.track_index_size()) != SP_ERROR_OK) { qLog(Error) << "Error when removing tracks!"; diff --git a/ext/libclementine-spotifyblob/spotifymessages.proto b/ext/libclementine-spotifyblob/spotifymessages.proto index dddb6e89d..fe3dd1be8 100644 --- a/ext/libclementine-spotifyblob/spotifymessages.proto +++ b/ext/libclementine-spotifyblob/spotifymessages.proto @@ -193,13 +193,15 @@ message PauseRequest { } message AddTracksToPlaylistRequest { - required int64 playlist_index = 1; - repeated string track_uri = 2; + required PlaylistType playlist_type = 1; + optional int64 playlist_index = 2; // Used if playlist_index == UserPlaylist + repeated string track_uri = 3; } message RemoveTracksFromPlaylistRequest { - required int64 playlist_index = 1; - repeated int64 track_index = 2; + required PlaylistType playlist_type = 1; + optional int64 playlist_index = 2; // Used if playlist_index == UserPlaylist + repeated int64 track_index = 3; } // NEXT_ID: 25 diff --git a/src/internet/spotify/spotifyserver.cpp b/src/internet/spotify/spotifyserver.cpp index b89df0418..ff56bdbbc 100644 --- a/src/internet/spotify/spotifyserver.cpp +++ b/src/internet/spotify/spotifyserver.cpp @@ -210,11 +210,22 @@ void SpotifyServer::LoadUserPlaylist(int index) { LoadPlaylist(pb::spotify::UserPlaylist, index); } -void SpotifyServer::AddSongsToPlaylist(int playlist_index, - const QList& songs_urls) { +void SpotifyServer::AddSongsToStarred(const QList& songs_urls) { + AddSongsToPlaylist(pb::spotify::Starred, songs_urls); +} + +void SpotifyServer::AddSongsToUserPlaylist(int playlist_index, + const QList& songs_urls) { + AddSongsToPlaylist(pb::spotify::UserPlaylist, songs_urls, playlist_index); +} + +void SpotifyServer::AddSongsToPlaylist(const pb::spotify::PlaylistType playlist_type, + const QList& songs_urls, + int playlist_index) { pb::spotify::Message message; pb::spotify::AddTracksToPlaylistRequest* req = message.mutable_add_tracks_to_playlist(); + req->set_playlist_type(playlist_type); req->set_playlist_index(playlist_index); for (const QUrl& song_url : songs_urls) { req->add_track_uri(DataCommaSizeFromQString(song_url.toString())); @@ -222,12 +233,25 @@ void SpotifyServer::AddSongsToPlaylist(int playlist_index, SendOrQueueMessage(message); } +void SpotifyServer::RemoveSongsFromStarred(const QList& songs_indices_to_remove) { + RemoveSongsFromPlaylist(pb::spotify::Starred, songs_indices_to_remove); +} + +void SpotifyServer::RemoveSongsFromUserPlaylist(int playlist_index, + const QList& songs_indices_to_remove) { + RemoveSongsFromPlaylist(pb::spotify::UserPlaylist, songs_indices_to_remove, playlist_index); +} + void SpotifyServer::RemoveSongsFromPlaylist( - int playlist_index, const QList& songs_indices_to_remove) { + const pb::spotify::PlaylistType playlist_type, + const QList& songs_indices_to_remove, int playlist_index) { pb::spotify::Message message; pb::spotify::RemoveTracksFromPlaylistRequest* req = message.mutable_remove_tracks_from_playlist(); - req->set_playlist_index(playlist_index); + req->set_playlist_type(playlist_type); + if (playlist_type == pb::spotify::UserPlaylist) { + req->set_playlist_index(playlist_index); + } for (int song_index : songs_indices_to_remove) { req->add_track_index(song_index); } diff --git a/src/internet/spotify/spotifyserver.h b/src/internet/spotify/spotifyserver.h index 027bf1f6d..9e2731e47 100644 --- a/src/internet/spotify/spotifyserver.h +++ b/src/internet/spotify/spotifyserver.h @@ -47,9 +47,11 @@ class SpotifyServer : public AbstractMessageHandler { void SyncInbox(); void LoadUserPlaylist(int index); void SyncUserPlaylist(int index); - void AddSongsToPlaylist(int playlist_index, const QList& songs_urls); - void RemoveSongsFromPlaylist(int playlist_index, - const QList& songs_indices_to_remove); + void AddSongsToStarred(const QList& songs_urls); + void AddSongsToUserPlaylist(int playlist_index, const QList& songs_urls); + void RemoveSongsFromUserPlaylist(int playlist_index, + const QList& songs_indices_to_remove); + void RemoveSongsFromStarred(const QList& songs_indices_to_remove); void StartPlaybackLater(const QString& uri, quint16 port); void Search(const QString& text, int limit, int limit_album = 0); void LoadImage(const QString& id); @@ -89,6 +91,14 @@ class SpotifyServer : public AbstractMessageHandler { private: void LoadPlaylist(pb::spotify::PlaylistType type, int index = -1); void SyncPlaylist(pb::spotify::PlaylistType type, int index, bool offline); + void AddSongsToPlaylist(const pb::spotify::PlaylistType playlist_type, + const QList& songs_urls, + // Used iff type is user_playlist + int playlist_index = -1); + void RemoveSongsFromPlaylist(const pb::spotify::PlaylistType playlist_type, + const QList& songs_indices_to_remove, + // Used iff type is user_playlist + int playlist_index = -1); void SendOrQueueMessage(const pb::spotify::Message& message); QTcpServer* server_; diff --git a/src/internet/spotify/spotifyservice.cpp b/src/internet/spotify/spotifyservice.cpp index d549fbaa9..f57f8d069 100644 --- a/src/internet/spotify/spotifyservice.cpp +++ b/src/internet/spotify/spotifyservice.cpp @@ -374,15 +374,24 @@ void SpotifyService::InstallBlob() { void SpotifyService::BlobDownloadFinished() { EnsureServerCreated(); } -void SpotifyService::AddCurrentSongToPlaylist(QAction* action) { +void SpotifyService::AddCurrentSongToUserPlaylist(QAction* action) { int playlist_index = action->data().toInt(); - AddSongsToPlaylist(playlist_index, QList() << current_song_url_); + AddSongsToUserPlaylist(playlist_index, QList() << current_song_url_); } -void SpotifyService::AddSongsToPlaylist(int playlist_index, +void SpotifyService::AddSongsToUserPlaylist(int playlist_index, const QList& songs_urls) { EnsureServerCreated(); - server_->AddSongsToPlaylist(playlist_index, songs_urls); + server_->AddSongsToUserPlaylist(playlist_index, songs_urls); +} + +void SpotifyService::AddCurrentSongToStarredPlaylist() { + AddSongsToStarred(QList() << current_song_url_); +} + +void SpotifyService::AddSongsToStarred(const QList& songs_urls) { + EnsureMenuCreated(); + server_->AddSongsToStarred(songs_urls); } void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) { @@ -407,6 +416,7 @@ void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) { starred_->setData(true, InternetModel::Role_CanLazyLoad); starred_->setData(InternetModel::PlayBehaviour_MultipleItems, InternetModel::Role_PlayBehaviour); + starred_->setData(true, InternetModel::Role_CanBeModified); inbox_ = new QStandardItem(IconLoader::Load("mail-message"), tr("Inbox")); inbox_->setData(Type_InboxPlaylist, InternetModel::Role_Type); @@ -424,6 +434,12 @@ void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) { root_->appendRow(toplist_); root_->appendRow(starred_); root_->appendRow(inbox_); + } else { + // Always reset starred playlist + // TODO: might be improved by including starred playlist in the response, + // and reloading it only when needed, like other playlists. + starred_->removeRows(0, starred_->rowCount()); + LazyPopulate(starred_); } // Don't do anything if the playlists haven't changed since last time. @@ -589,6 +605,12 @@ QList SpotifyService::playlistitem_actions(const Song& song) { delete action; } + QAction* add_to_starred = new QAction(QIcon(":/star-on.png"), + tr("Add to Spotify starred"), this); + connect(add_to_starred, SIGNAL(triggered()), + SLOT(AddCurrentSongToStarredPlaylist())); + playlistitem_actions_.append(add_to_starred); + // Create a menu with 'add to playlist' actions for each Spotify playlist QAction* add_to_playlists = new QAction(IconLoader::Load("list-add"), tr("Add to Spotify playlists"), this); @@ -602,7 +624,7 @@ QList SpotifyService::playlistitem_actions(const Song& song) { playlists_menu->addAction(add_to_playlist); } connect(playlists_menu, SIGNAL(triggered(QAction*)), - SLOT(AddCurrentSongToPlaylist(QAction*))); + SLOT(AddCurrentSongToUserPlaylist(QAction*))); add_to_playlists->setMenu(playlists_menu); playlistitem_actions_.append(add_to_playlists); @@ -823,7 +845,7 @@ void SpotifyService::DropMimeData(const QMimeData* data, } if (!q_playlist_index.isValid()) return; - AddSongsToPlaylist(q_playlist_index.toInt(), data->urls()); + AddSongsToUserPlaylist(q_playlist_index.toInt(), data->urls()); } void SpotifyService::LoadImage(const QString& id) { @@ -885,10 +907,16 @@ void SpotifyService::ShowConfig() { void SpotifyService::RemoveCurrentFromPlaylist() { const QModelIndexList& indexes(model()->selected_indexes()); QMap> playlists_songs_indices; + QList starred_songs_indices; + for (const QModelIndex& index : indexes) { - if (index.parent().data(InternetModel::Role_Type).toInt() != + bool is_starred = false; + if (index.parent().data(InternetModel::Role_Type).toInt() == + Type_StarredPlaylist) { + is_starred = true; + } else if (index.parent().data(InternetModel::Role_Type).toInt() != InternetModel::Type_UserPlaylist) { - continue; + continue; } if (index.data(InternetModel::Role_Type).toInt() != @@ -896,21 +924,33 @@ void SpotifyService::RemoveCurrentFromPlaylist() { continue; } - int playlist_index = index.parent().data(Role_UserPlaylistIndex).toInt(); int song_index = index.row(); - playlists_songs_indices[playlist_index] << song_index; + if (is_starred) { + starred_songs_indices << song_index; + } else { + int playlist_index = index.parent().data(Role_UserPlaylistIndex).toInt(); + playlists_songs_indices[playlist_index] << song_index; + } } for (QMap>::const_iterator it = playlists_songs_indices.constBegin(); it != playlists_songs_indices.constEnd(); ++it) { - RemoveSongsFromPlaylist(it.key(), it.value()); + RemoveSongsFromUserPlaylist(it.key(), it.value()); + } + if (!starred_songs_indices.isEmpty()) { + RemoveSongsFromStarred(starred_songs_indices); } } -void SpotifyService::RemoveSongsFromPlaylist( +void SpotifyService::RemoveSongsFromUserPlaylist( int playlist_index, const QList& songs_indices_to_remove) { - server_->RemoveSongsFromPlaylist(playlist_index, songs_indices_to_remove); + server_->RemoveSongsFromUserPlaylist(playlist_index, songs_indices_to_remove); +} + +void SpotifyService::RemoveSongsFromStarred( + const QList& songs_indices_to_remove) { + server_->RemoveSongsFromStarred(songs_indices_to_remove); } void SpotifyService::Logout() { diff --git a/src/internet/spotify/spotifyservice.h b/src/internet/spotify/spotifyservice.h index a2fe2baf6..1deaae7be 100644 --- a/src/internet/spotify/spotifyservice.h +++ b/src/internet/spotify/spotifyservice.h @@ -112,7 +112,8 @@ class SpotifyService : public InternetService { const google::protobuf::RepeatedPtrField& tracks); void FillPlaylist(QStandardItem* item, const pb::spotify::LoadPlaylistResponse& response); - void AddSongsToPlaylist(int playlist_index, const QList& songs_urls); + void AddSongsToUserPlaylist(int playlist_index, const QList& songs_urls); + void AddSongsToStarred(const QList& songs_urls); void EnsureMenuCreated(); // Create a new "show config" action. The caller is responsible for deleting // the pointer (or adding it to menu or anything else that will take ownership @@ -129,9 +130,11 @@ class SpotifyService : public InternetService { void BlobProcessError(QProcess::ProcessError error); void LoginCompleted(bool success, const QString& error, pb::spotify::LoginResponse_Error error_code); - void AddCurrentSongToPlaylist(QAction* action); - void RemoveSongsFromPlaylist(int playlist_index, + void AddCurrentSongToUserPlaylist(QAction* action); + void AddCurrentSongToStarredPlaylist(); + void RemoveSongsFromUserPlaylist(int playlist_index, const QList& songs_indices_to_remove); + void RemoveSongsFromStarred(const QList& songs_indices_to_remove); void PlaylistsUpdated(const pb::spotify::Playlists& response); void InboxLoaded(const pb::spotify::LoadPlaylistResponse& response); void StarredLoaded(const pb::spotify::LoadPlaylistResponse& response);