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:
parent
60d82e212d
commit
39db4dbefe
@ -21,6 +21,7 @@
|
|||||||
#include "spotifyclient.h"
|
#include "spotifyclient.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@ -295,6 +296,8 @@ void SpotifyClient::MessageArrived(const pb::spotify::Message& message) {
|
|||||||
BrowseToplist(message.browse_toplist_request());
|
BrowseToplist(message.browse_toplist_request());
|
||||||
} else if (message.has_pause_request()) {
|
} else if (message.has_pause_request()) {
|
||||||
SetPaused(message.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();
|
pb::spotify::Playlists::Playlist* msg = response->add_playlist();
|
||||||
msg->set_index(i);
|
msg->set_index(i);
|
||||||
msg->set_name(sp_playlist_name(playlist));
|
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_offline_status offline_status =
|
||||||
sp_playlist_get_offline_status(session_, playlist);
|
sp_playlist_get_offline_status(session_, playlist);
|
||||||
@ -449,6 +455,7 @@ void SpotifyClient::SendPlaylistList() {
|
|||||||
} else if (offline_status == SP_PLAYLIST_OFFLINE_STATUS_WAITING) {
|
} else if (offline_status == SP_PLAYLIST_OFFLINE_STATUS_WAITING) {
|
||||||
msg->set_download_progress(0);
|
msg->set_download_progress(0);
|
||||||
}
|
}
|
||||||
|
msg->set_nb_tracks(sp_playlist_num_tracks(playlist));
|
||||||
}
|
}
|
||||||
|
|
||||||
SendMessage(message);
|
SendMessage(message);
|
||||||
@ -593,6 +600,45 @@ void SpotifyClient::PlaylistStateChangedForGetPlaylists(sp_playlist* pl,
|
|||||||
me->SendPlaylistList();
|
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) {
|
void SpotifyClient::ConvertTrack(sp_track* track, pb::spotify::Track* pb) {
|
||||||
sp_album* album = sp_track_album(track);
|
sp_album* album = sp_track_album(track);
|
||||||
|
|
||||||
|
@ -123,6 +123,7 @@ class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> {
|
|||||||
void Search(const pb::spotify::SearchRequest& req);
|
void Search(const pb::spotify::SearchRequest& req);
|
||||||
void LoadPlaylist(const pb::spotify::LoadPlaylistRequest& req);
|
void LoadPlaylist(const pb::spotify::LoadPlaylistRequest& req);
|
||||||
void SyncPlaylist(const pb::spotify::SyncPlaylistRequest& req);
|
void SyncPlaylist(const pb::spotify::SyncPlaylistRequest& req);
|
||||||
|
void AddTracksToPlaylist(const pb::spotify::AddTracksToPlaylistRequest& req);
|
||||||
void StartPlayback(const pb::spotify::PlaybackRequest& req);
|
void StartPlayback(const pb::spotify::PlaybackRequest& req);
|
||||||
void Seek(qint64 offset_nsec);
|
void Seek(qint64 offset_nsec);
|
||||||
void LoadImage(const QString& id_b64);
|
void LoadImage(const QString& id_b64);
|
||||||
|
@ -46,9 +46,12 @@ message Playlists {
|
|||||||
message Playlist {
|
message Playlist {
|
||||||
required int32 index = 1;
|
required int32 index = 1;
|
||||||
required string name = 2;
|
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.
|
// Offline sync progress between 0-100.
|
||||||
optional int32 download_progress = 4;
|
optional int32 download_progress = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
repeated Playlist playlist = 1;
|
repeated Playlist playlist = 1;
|
||||||
@ -191,7 +194,12 @@ message PauseRequest {
|
|||||||
optional bool paused = 1 [default = false];
|
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 {
|
message Message {
|
||||||
// Not currently used
|
// Not currently used
|
||||||
optional int32 id = 18;
|
optional int32 id = 18;
|
||||||
@ -217,4 +225,5 @@ message Message {
|
|||||||
optional BrowseToplistResponse browse_toplist_response = 20;
|
optional BrowseToplistResponse browse_toplist_response = 20;
|
||||||
optional PauseRequest pause_request = 21;
|
optional PauseRequest pause_request = 21;
|
||||||
optional SeekCompleted seek_completed = 22;
|
optional SeekCompleted seek_completed = 22;
|
||||||
|
optional AddTracksToPlaylistRequest add_tracks_to_playlist = 23;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <QTcpServer>
|
#include <QTcpServer>
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
SpotifyServer::SpotifyServer(QObject* parent)
|
SpotifyServer::SpotifyServer(QObject* parent)
|
||||||
: AbstractMessageHandler<pb::spotify::Message>(nullptr, parent),
|
: AbstractMessageHandler<pb::spotify::Message>(nullptr, parent),
|
||||||
@ -206,6 +207,18 @@ void SpotifyServer::LoadUserPlaylist(int index) {
|
|||||||
LoadPlaylist(pb::spotify::UserPlaylist, 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) {
|
void SpotifyServer::StartPlaybackLater(const QString& uri, quint16 port) {
|
||||||
QTimer* timer = new QTimer(this);
|
QTimer* timer = new QTimer(this);
|
||||||
connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater()));
|
connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater()));
|
||||||
|
@ -43,6 +43,7 @@ class SpotifyServer : public AbstractMessageHandler<pb::spotify::Message> {
|
|||||||
void SyncInbox();
|
void SyncInbox();
|
||||||
void LoadUserPlaylist(int index);
|
void LoadUserPlaylist(int index);
|
||||||
void SyncUserPlaylist(int index);
|
void SyncUserPlaylist(int index);
|
||||||
|
void AddSongsToPlaylist(int playlist_index, const QList<QUrl>& songs_urls);
|
||||||
void StartPlaybackLater(const QString& uri, quint16 port);
|
void StartPlaybackLater(const QString& uri, quint16 port);
|
||||||
void Search(const QString& text, int limit, int limit_album = 0);
|
void Search(const QString& text, int limit, int limit_album = 0);
|
||||||
void LoadImage(const QString& id);
|
void LoadImage(const QString& id);
|
||||||
|
@ -345,6 +345,17 @@ void SpotifyService::InstallBlob() {
|
|||||||
|
|
||||||
void SpotifyService::BlobDownloadFinished() { EnsureServerCreated(); }
|
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) {
|
void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) {
|
||||||
if (login_task_id_) {
|
if (login_task_id_) {
|
||||||
app_->task_manager()->SetTaskFinished(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";
|
qLog(Debug) << "Playlists haven't changed - not updating";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
qLog(Debug) << "Playlist have changed: updating";
|
||||||
|
|
||||||
// Remove and recreate the other playlists
|
// Remove and recreate the other playlists
|
||||||
for (QStandardItem* item : 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) {
|
for (int i = 0; i < response.playlist_size(); ++i) {
|
||||||
const pb::spotify::Playlists::Playlist& msg = response.playlist(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(InternetModel::Type_UserPlaylist, InternetModel::Role_Type);
|
||||||
item->setData(true, InternetModel::Role_CanLazyLoad);
|
item->setData(true, InternetModel::Role_CanLazyLoad);
|
||||||
item->setData(msg.index(), Role_UserPlaylistIndex);
|
item->setData(msg.index(), Role_UserPlaylistIndex);
|
||||||
|
item->setData(msg.is_mine(), Role_UserPlaylistIsMine);
|
||||||
item->setData(InternetModel::PlayBehaviour_MultipleItems,
|
item->setData(InternetModel::PlayBehaviour_MultipleItems,
|
||||||
InternetModel::Role_PlayBehaviour);
|
InternetModel::Role_PlayBehaviour);
|
||||||
|
|
||||||
@ -416,8 +434,8 @@ void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpotifyService::DoPlaylistsDiffer(const pb::spotify::Playlists& response)
|
bool SpotifyService::DoPlaylistsDiffer(
|
||||||
const {
|
const pb::spotify::Playlists& response) const {
|
||||||
if (playlists_.count() != response.playlist_size()) {
|
if (playlists_.count() != response.playlist_size()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -433,6 +451,10 @@ bool SpotifyService::DoPlaylistsDiffer(const pb::spotify::Playlists& response)
|
|||||||
if (QStringFromStdString(msg.name()) != item->text()) {
|
if (QStringFromStdString(msg.name()) != item->text()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg.nb_tracks() != item->rowCount()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -528,6 +550,37 @@ void SpotifyService::SongFromProtobuf(const pb::spotify::Track& track,
|
|||||||
song->set_filesize(0);
|
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 {
|
QWidget* SpotifyService::HeaderWidget() const {
|
||||||
if (IsLoggedIn()) return search_box_;
|
if (IsLoggedIn()) return search_box_;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -29,7 +29,10 @@ class SpotifyService : public InternetService {
|
|||||||
Type_Toplist,
|
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.
|
// Values are persisted - don't change.
|
||||||
enum LoginState {
|
enum LoginState {
|
||||||
@ -53,6 +56,7 @@ class SpotifyService : public InternetService {
|
|||||||
void ShowContextMenu(const QPoint& global_pos);
|
void ShowContextMenu(const QPoint& global_pos);
|
||||||
void ItemDoubleClicked(QStandardItem* item);
|
void ItemDoubleClicked(QStandardItem* item);
|
||||||
void DropMimeData(const QMimeData* data, const QModelIndex& index);
|
void DropMimeData(const QMimeData* data, const QModelIndex& index);
|
||||||
|
QList<QAction*> playlistitem_actions(const Song& song) override;
|
||||||
QWidget* HeaderWidget() const;
|
QWidget* HeaderWidget() const;
|
||||||
|
|
||||||
void Logout();
|
void Logout();
|
||||||
@ -88,6 +92,7 @@ signals:
|
|||||||
const google::protobuf::RepeatedPtrField<pb::spotify::Track>& tracks);
|
const google::protobuf::RepeatedPtrField<pb::spotify::Track>& tracks);
|
||||||
void FillPlaylist(QStandardItem* item,
|
void FillPlaylist(QStandardItem* item,
|
||||||
const pb::spotify::LoadPlaylistResponse& response);
|
const pb::spotify::LoadPlaylistResponse& response);
|
||||||
|
void AddSongsToPlaylist(int playlist_index, const QList<QUrl>& songs_urls);
|
||||||
void EnsureMenuCreated();
|
void EnsureMenuCreated();
|
||||||
void ClearSearchResults();
|
void ClearSearchResults();
|
||||||
|
|
||||||
@ -100,6 +105,7 @@ signals:
|
|||||||
void BlobProcessError(QProcess::ProcessError error);
|
void BlobProcessError(QProcess::ProcessError error);
|
||||||
void LoginCompleted(bool success, const QString& error,
|
void LoginCompleted(bool success, const QString& error,
|
||||||
pb::spotify::LoginResponse_Error error_code);
|
pb::spotify::LoginResponse_Error error_code);
|
||||||
|
void AddCurrentSongToPlaylist(QAction* action);
|
||||||
void PlaylistsUpdated(const pb::spotify::Playlists& response);
|
void PlaylistsUpdated(const pb::spotify::Playlists& response);
|
||||||
void InboxLoaded(const pb::spotify::LoadPlaylistResponse& response);
|
void InboxLoaded(const pb::spotify::LoadPlaylistResponse& response);
|
||||||
void StarredLoaded(const pb::spotify::LoadPlaylistResponse& response);
|
void StarredLoaded(const pb::spotify::LoadPlaylistResponse& response);
|
||||||
@ -134,6 +140,8 @@ signals:
|
|||||||
QMenu* context_menu_;
|
QMenu* context_menu_;
|
||||||
QMenu* playlist_context_menu_;
|
QMenu* playlist_context_menu_;
|
||||||
QAction* playlist_sync_action_;
|
QAction* playlist_sync_action_;
|
||||||
|
QList<QAction*> playlistitem_actions_;
|
||||||
|
QUrl current_song_url_;
|
||||||
|
|
||||||
SearchBoxWidget* search_box_;
|
SearchBoxWidget* search_box_;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user