Add the ability to add a Spotify track to a Spotify playlist through context menu.

TODO: drag and drop (i.e. implement DropMimeData), update special playlist (Favorites), remove from playlist.
This commit is contained in:
Arnaud Bienner 2014-09-22 22:40:21 +02:00
parent 60d82e212d
commit 39db4dbefe
7 changed files with 138 additions and 7 deletions

View File

@ -21,6 +21,7 @@
#include "spotifyclient.h"
#include <algorithm>
#include <memory>
#include <QCoreApplication>
#include <QDir>
@ -295,6 +296,8 @@ void SpotifyClient::MessageArrived(const pb::spotify::Message& message) {
BrowseToplist(message.browse_toplist_request());
} else if (message.has_pause_request()) {
SetPaused(message.pause_request());
} else if (message.has_add_tracks_to_playlist()) {
AddTracksToPlaylist(message.add_tracks_to_playlist());
}
}
@ -438,6 +441,9 @@ void SpotifyClient::SendPlaylistList() {
pb::spotify::Playlists::Playlist* msg = response->add_playlist();
msg->set_index(i);
msg->set_name(sp_playlist_name(playlist));
sp_user* playlist_owner = sp_playlist_owner(playlist);
msg->set_is_mine(sp_session_user(session_) == playlist_owner);
msg->set_owner(sp_user_display_name(playlist_owner));
sp_playlist_offline_status offline_status =
sp_playlist_get_offline_status(session_, playlist);
@ -449,6 +455,7 @@ void SpotifyClient::SendPlaylistList() {
} else if (offline_status == SP_PLAYLIST_OFFLINE_STATUS_WAITING) {
msg->set_download_progress(0);
}
msg->set_nb_tracks(sp_playlist_num_tracks(playlist));
}
SendMessage(message);
@ -593,6 +600,45 @@ void SpotifyClient::PlaylistStateChangedForGetPlaylists(sp_playlist* pl,
me->SendPlaylistList();
}
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);
if (!playlist) {
qLog(Error) << "Playlist " << playlist_index << "not found";
return;
}
// Get the tracks we want to add
std::unique_ptr<sp_track*[]> tracks_array (new sp_track*[req.track_uri_size()]);
for (int i = 0; i < req.track_uri_size(); ++i) {
sp_link* track_link = sp_link_create_from_string(req.track_uri(i).c_str());
sp_track* track = sp_link_as_track(track_link);
sp_track_add_ref(track);
sp_link_release(track_link);
if (!track) {
qLog(Error) << "Track" << QString::fromStdString(req.track_uri(i)) << "not found";
}
tracks_array[i] = track;
}
// Actually add the tracks to the playlist
if (sp_playlist_add_tracks(playlist, tracks_array.get(),
req.track_uri_size(),
0 /* TODO: don't insert at a hardcoded position */,
session_) != SP_ERROR_OK) {
qLog(Error) << "Error when adding tracks!";
}
// Clean everything
for (int i = 0; i < req.track_uri_size(); ++i) {
sp_track_release(tracks_array[i]);
}
}
void SpotifyClient::ConvertTrack(sp_track* track, pb::spotify::Track* pb) {
sp_album* album = sp_track_album(track);

View File

@ -123,6 +123,7 @@ class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> {
void Search(const pb::spotify::SearchRequest& req);
void LoadPlaylist(const pb::spotify::LoadPlaylistRequest& req);
void SyncPlaylist(const pb::spotify::SyncPlaylistRequest& req);
void AddTracksToPlaylist(const pb::spotify::AddTracksToPlaylistRequest& req);
void StartPlayback(const pb::spotify::PlaybackRequest& req);
void Seek(qint64 offset_nsec);
void LoadImage(const QString& id_b64);

View File

@ -46,9 +46,12 @@ message Playlists {
message Playlist {
required int32 index = 1;
required string name = 2;
required bool is_offline = 3;
required int32 nb_tracks = 3;
required bool is_mine = 4;
required string owner= 5;
required bool is_offline = 6;
// Offline sync progress between 0-100.
optional int32 download_progress = 4;
optional int32 download_progress = 7;
}
repeated Playlist playlist = 1;
@ -191,7 +194,12 @@ message PauseRequest {
optional bool paused = 1 [default = false];
}
// NEXT_ID: 23
message AddTracksToPlaylistRequest {
required int64 playlist_index = 1;
repeated string track_uri = 2;
}
// NEXT_ID: 24
message Message {
// Not currently used
optional int32 id = 18;
@ -217,4 +225,5 @@ message Message {
optional BrowseToplistResponse browse_toplist_response = 20;
optional PauseRequest pause_request = 21;
optional SeekCompleted seek_completed = 22;
optional AddTracksToPlaylistRequest add_tracks_to_playlist = 23;
}

View File

@ -24,6 +24,7 @@
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimer>
#include <QUrl>
SpotifyServer::SpotifyServer(QObject* parent)
: AbstractMessageHandler<pb::spotify::Message>(nullptr, parent),
@ -206,6 +207,18 @@ void SpotifyServer::LoadUserPlaylist(int index) {
LoadPlaylist(pb::spotify::UserPlaylist, index);
}
void SpotifyServer::AddSongsToPlaylist(int playlist_index,
const QList<QUrl>& songs_urls) {
pb::spotify::Message message;
pb::spotify::AddTracksToPlaylistRequest* req =
message.mutable_add_tracks_to_playlist();
req->set_playlist_index(playlist_index);
for (const QUrl& song_url : songs_urls) {
req->add_track_uri(DataCommaSizeFromQString(song_url.toString()));
}
SendOrQueueMessage(message);
}
void SpotifyServer::StartPlaybackLater(const QString& uri, quint16 port) {
QTimer* timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater()));

View File

@ -43,6 +43,7 @@ class SpotifyServer : public AbstractMessageHandler<pb::spotify::Message> {
void SyncInbox();
void LoadUserPlaylist(int index);
void SyncUserPlaylist(int index);
void AddSongsToPlaylist(int playlist_index, const QList<QUrl>& songs_urls);
void StartPlaybackLater(const QString& uri, quint16 port);
void Search(const QString& text, int limit, int limit_album = 0);
void LoadImage(const QString& id);

View File

@ -345,6 +345,17 @@ void SpotifyService::InstallBlob() {
void SpotifyService::BlobDownloadFinished() { EnsureServerCreated(); }
void SpotifyService::AddCurrentSongToPlaylist(QAction* action) {
int playlist_index = action->data().toInt();
AddSongsToPlaylist(playlist_index, QList<QUrl>() << current_song_url_);
}
void SpotifyService::AddSongsToPlaylist(int playlist_index,
const QList<QUrl>& songs_urls) {
EnsureServerCreated();
server_->AddSongsToPlaylist(playlist_index, songs_urls);
}
void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) {
if (login_task_id_) {
app_->task_manager()->SetTaskFinished(login_task_id_);
@ -391,6 +402,7 @@ void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) {
qLog(Debug) << "Playlists haven't changed - not updating";
return;
}
qLog(Debug) << "Playlist have changed: updating";
// Remove and recreate the other playlists
for (QStandardItem* item : playlists_) {
@ -401,10 +413,16 @@ void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) {
for (int i = 0; i < response.playlist_size(); ++i) {
const pb::spotify::Playlists::Playlist& msg = response.playlist(i);
QStandardItem* item = new QStandardItem(QStringFromStdString(msg.name()));
QString playlist_title = QStringFromStdString(msg.name());
if (!msg.is_mine()) {
const std::string& owner = msg.owner();
playlist_title += tr(", by ") + QString::fromUtf8(owner.c_str(), owner.size());
}
QStandardItem* item = new QStandardItem(playlist_title);
item->setData(InternetModel::Type_UserPlaylist, InternetModel::Role_Type);
item->setData(true, InternetModel::Role_CanLazyLoad);
item->setData(msg.index(), Role_UserPlaylistIndex);
item->setData(msg.is_mine(), Role_UserPlaylistIsMine);
item->setData(InternetModel::PlayBehaviour_MultipleItems,
InternetModel::Role_PlayBehaviour);
@ -416,8 +434,8 @@ void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) {
}
}
bool SpotifyService::DoPlaylistsDiffer(const pb::spotify::Playlists& response)
const {
bool SpotifyService::DoPlaylistsDiffer(
const pb::spotify::Playlists& response) const {
if (playlists_.count() != response.playlist_size()) {
return true;
}
@ -433,6 +451,10 @@ bool SpotifyService::DoPlaylistsDiffer(const pb::spotify::Playlists& response)
if (QStringFromStdString(msg.name()) != item->text()) {
return true;
}
if (msg.nb_tracks() != item->rowCount()) {
return true;
}
}
return false;
@ -528,6 +550,37 @@ void SpotifyService::SongFromProtobuf(const pb::spotify::Track& track,
song->set_filesize(0);
}
QList<QAction*> SpotifyService::playlistitem_actions(const Song& song) {
// Clear previous actions
while (!playlistitem_actions_.isEmpty()) {
QAction* action = playlistitem_actions_.takeFirst();
delete action->menu();
delete action;
}
// 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);
QMenu* playlists_menu = new QMenu();
for (const QStandardItem* playlist_item : playlists_) {
if (!playlist_item->data(Role_UserPlaylistIsMine).toBool()) {
continue;
}
QAction* add_to_playlist = new QAction(playlist_item->text(), this);
add_to_playlist->setData(playlist_item->data(Role_UserPlaylistIndex));
playlists_menu->addAction(add_to_playlist);
}
connect(playlists_menu, SIGNAL(triggered(QAction*)),
SLOT(AddCurrentSongToPlaylist(QAction*)));
add_to_playlists->setMenu(playlists_menu);
playlistitem_actions_.append(add_to_playlists);
// Keep in mind the current song URL
current_song_url_ = song.url();
return playlistitem_actions_;
}
QWidget* SpotifyService::HeaderWidget() const {
if (IsLoggedIn()) return search_box_;
return nullptr;

View File

@ -29,7 +29,10 @@ class SpotifyService : public InternetService {
Type_Toplist,
};
enum Role { Role_UserPlaylistIndex = InternetModel::RoleCount, };
enum Role {
Role_UserPlaylistIndex = InternetModel::RoleCount,
Role_UserPlaylistIsMine, // Is this playlist owned by the user currently logged-in?
};
// Values are persisted - don't change.
enum LoginState {
@ -53,6 +56,7 @@ class SpotifyService : public InternetService {
void ShowContextMenu(const QPoint& global_pos);
void ItemDoubleClicked(QStandardItem* item);
void DropMimeData(const QMimeData* data, const QModelIndex& index);
QList<QAction*> playlistitem_actions(const Song& song) override;
QWidget* HeaderWidget() const;
void Logout();
@ -88,6 +92,7 @@ signals:
const google::protobuf::RepeatedPtrField<pb::spotify::Track>& tracks);
void FillPlaylist(QStandardItem* item,
const pb::spotify::LoadPlaylistResponse& response);
void AddSongsToPlaylist(int playlist_index, const QList<QUrl>& songs_urls);
void EnsureMenuCreated();
void ClearSearchResults();
@ -100,6 +105,7 @@ signals:
void BlobProcessError(QProcess::ProcessError error);
void LoginCompleted(bool success, const QString& error,
pb::spotify::LoginResponse_Error error_code);
void AddCurrentSongToPlaylist(QAction* action);
void PlaylistsUpdated(const pb::spotify::Playlists& response);
void InboxLoaded(const pb::spotify::LoadPlaylistResponse& response);
void StarredLoaded(const pb::spotify::LoadPlaylistResponse& response);
@ -134,6 +140,8 @@ signals:
QMenu* context_menu_;
QMenu* playlist_context_menu_;
QAction* playlist_sync_action_;
QList<QAction*> playlistitem_actions_;
QUrl current_song_url_;
SearchBoxWidget* search_box_;