Get the list of playlists from spotify. Also clean up the logging in the client, rename and refactor some classes.
This commit is contained in:
parent
e152e3a3e3
commit
c4f1b3f002
@ -307,5 +307,8 @@
|
||||
<file>last.fm/as_disabled.png</file>
|
||||
<file>last.fm/as_light.png</file>
|
||||
<file>icons/32x32/tools-wizard.png</file>
|
||||
<file>icons/22x22/mail-message.png</file>
|
||||
<file>icons/32x32/mail-message.png</file>
|
||||
<file>icons/48x48/mail-message.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
data/icons/22x22/mail-message.png
Normal file
BIN
data/icons/22x22/mail-message.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 569 B |
BIN
data/icons/32x32/mail-message.png
Normal file
BIN
data/icons/32x32/mail-message.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 605 B |
BIN
data/icons/48x48/mail-message.png
Normal file
BIN
data/icons/48x48/mail-message.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 955 B |
@ -8,28 +8,18 @@ link_directories(${SPOTIFY_LIBRARY_DIRS})
|
||||
set(EXECUTABLE_OUTPUT_PATH ..)
|
||||
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
spotifyblob.cpp
|
||||
spotifyclient.cpp
|
||||
|
||||
../src/core/logging.cpp
|
||||
set(COMMON_SOURCES
|
||||
spotifymessageutils.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
spotifyblob.h
|
||||
spotifyclient.h
|
||||
set(COMMON_MESSAGES
|
||||
spotifymessages.proto
|
||||
)
|
||||
|
||||
set(MESSAGES
|
||||
messages.proto
|
||||
)
|
||||
|
||||
qt4_wrap_cpp(MOC ${HEADERS})
|
||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
||||
|
||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${COMMON_MESSAGES})
|
||||
|
||||
add_library(clementine-spotifyblob-messages STATIC
|
||||
${COMMON_SOURCES}
|
||||
${PROTO_SOURCES}
|
||||
)
|
||||
|
||||
@ -39,6 +29,19 @@ target_link_libraries(clementine-spotifyblob-messages
|
||||
)
|
||||
|
||||
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
spotifyclient.cpp
|
||||
|
||||
../src/core/logging.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
spotifyclient.h
|
||||
)
|
||||
|
||||
qt4_wrap_cpp(MOC ${HEADERS})
|
||||
|
||||
add_executable(clementine-spotifyblob
|
||||
${SOURCES}
|
||||
${MOC}
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QStringList>
|
||||
|
||||
#include "spotifyblob.h"
|
||||
#include "spotifyclient.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
@ -17,8 +16,6 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
SpotifyClient client;
|
||||
SpotifyBlob blob(&client);
|
||||
|
||||
client.Init(arguments[1].toInt());
|
||||
|
||||
return a.exec();
|
||||
|
@ -1,17 +0,0 @@
|
||||
message LoginRequest {
|
||||
required string username = 1;
|
||||
required string password = 2;
|
||||
}
|
||||
|
||||
message LoginResponse {
|
||||
required bool success = 1;
|
||||
required string error = 2;
|
||||
}
|
||||
|
||||
message RequestMessage {
|
||||
optional LoginRequest login_request = 1;
|
||||
}
|
||||
|
||||
message ResponseMessage {
|
||||
optional LoginResponse login_response = 1;
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
#include "spotifyblob.h"
|
||||
#include "spotifyclient.h"
|
||||
#include "spotifykey.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QTimer>
|
||||
#include <QtDebug>
|
||||
|
||||
SpotifyBlob::SpotifyBlob(SpotifyClient* client, QObject* parent)
|
||||
: QObject(parent),
|
||||
client_(client),
|
||||
session_(NULL),
|
||||
events_timer_(new QTimer(this)) {
|
||||
memset(&spotify_callbacks_, 0, sizeof(spotify_callbacks_));
|
||||
memset(&spotify_config_, 0, sizeof(spotify_config_));
|
||||
|
||||
spotify_callbacks_.logged_in = &LoggedIn;
|
||||
spotify_callbacks_.notify_main_thread = &NotifyMainThread;
|
||||
spotify_callbacks_.log_message = &LogMessage;
|
||||
|
||||
spotify_config_.api_version = SPOTIFY_API_VERSION; // From libspotify/api.h
|
||||
spotify_config_.cache_location = strdup(QDir::tempPath().toLocal8Bit().constData());
|
||||
spotify_config_.settings_location = strdup(QDir::tempPath().toLocal8Bit().constData());
|
||||
spotify_config_.application_key = g_appkey;
|
||||
spotify_config_.application_key_size = g_appkey_size;
|
||||
spotify_config_.callbacks = &spotify_callbacks_;
|
||||
spotify_config_.userdata = this;
|
||||
spotify_config_.user_agent = "Clementine Player";
|
||||
|
||||
events_timer_->setSingleShot(true);
|
||||
connect(events_timer_, SIGNAL(timeout()), SLOT(ProcessEvents()));
|
||||
|
||||
connect(client, SIGNAL(Login(QString,QString)), SLOT(Login(QString,QString)));
|
||||
}
|
||||
|
||||
SpotifyBlob::~SpotifyBlob() {
|
||||
if (session_) {
|
||||
sp_session_release(session_);
|
||||
}
|
||||
|
||||
free(const_cast<char*>(spotify_config_.cache_location));
|
||||
free(const_cast<char*>(spotify_config_.settings_location));
|
||||
}
|
||||
|
||||
void SpotifyBlob::Login(const QString& username, const QString& password) {
|
||||
sp_error error = sp_session_create(&spotify_config_, &session_);
|
||||
if (error != SP_ERROR_OK) {
|
||||
qWarning() << "Failed to create session" << sp_error_message(error);
|
||||
client_->LoginCompleted(false, sp_error_message(error));
|
||||
return;
|
||||
}
|
||||
|
||||
sp_session_login(session_, username.toUtf8().constData(), password.toUtf8().constData());
|
||||
}
|
||||
|
||||
void SpotifyBlob::LoggedIn(sp_session* session, sp_error error) {
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
SpotifyBlob* me = reinterpret_cast<SpotifyBlob*>(sp_session_userdata(session));
|
||||
me->LoggedIn(error);
|
||||
}
|
||||
|
||||
void SpotifyBlob::LoggedIn(sp_error error) {
|
||||
if (error != SP_ERROR_OK) {
|
||||
qWarning() << "Failed to login" << sp_error_message(error);
|
||||
}
|
||||
|
||||
client_->LoginCompleted(error == SP_ERROR_OK, sp_error_message(error));
|
||||
}
|
||||
|
||||
void SpotifyBlob::NotifyMainThread(sp_session* session) {
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
SpotifyBlob* me = reinterpret_cast<SpotifyBlob*>(sp_session_userdata(session));
|
||||
me->Notify();
|
||||
}
|
||||
|
||||
// Called by spotify from an internal thread to notify us that its events need processing.
|
||||
void SpotifyBlob::Notify() {
|
||||
metaObject()->invokeMethod(this, "ProcessEvents", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void SpotifyBlob::ProcessEvents() {
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
int next_timeout_ms;
|
||||
sp_session_process_events(session_, &next_timeout_ms);
|
||||
qDebug() << next_timeout_ms << events_timer_;
|
||||
events_timer_->start(next_timeout_ms);
|
||||
qDebug() << "Started";
|
||||
}
|
||||
|
||||
void SpotifyBlob::LogMessage(sp_session* session, const char* data) {
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
}
|
||||
|
||||
void SpotifyBlob::Search(const QString& query) {
|
||||
sp_search_create(
|
||||
session_,
|
||||
query.toUtf8().constData(),
|
||||
0, // track offset
|
||||
10, // track count
|
||||
0, // album offset
|
||||
10, // album count
|
||||
0, // artist offset
|
||||
10, // artist count
|
||||
&SearchComplete,
|
||||
this);
|
||||
}
|
||||
|
||||
void SpotifyBlob::SearchComplete(sp_search* result, void* userdata) {
|
||||
sp_error error = sp_search_error(result);
|
||||
if (error != SP_ERROR_OK) {
|
||||
qWarning() << "Search failed";
|
||||
sp_search_release(result);
|
||||
return;
|
||||
}
|
||||
|
||||
int artists = sp_search_num_artists(result);
|
||||
for (int i = 0; i < artists; ++i) {
|
||||
sp_artist* artist = sp_search_artist(result, i);
|
||||
qDebug() << "Found artist:" << sp_artist_name(artist);
|
||||
}
|
||||
|
||||
sp_search_release(result);
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
#ifndef SPOTIFY_H
|
||||
#define SPOTIFY_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <libspotify/api.h>
|
||||
|
||||
class SpotifyClient;
|
||||
|
||||
class QTimer;
|
||||
|
||||
class SpotifyBlob : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpotifyBlob(SpotifyClient* client, QObject* parent = 0);
|
||||
~SpotifyBlob();
|
||||
|
||||
private slots:
|
||||
void ProcessEvents();
|
||||
void Login(const QString& username, const QString& password);
|
||||
void Search(const QString& query);
|
||||
|
||||
private:
|
||||
// Spotify callbacks.
|
||||
static void LoggedIn(sp_session* session, sp_error error);
|
||||
static void NotifyMainThread(sp_session* session);
|
||||
static void LogMessage(sp_session* session, const char* data);
|
||||
|
||||
static void SearchComplete(sp_search* result, void* userdata);
|
||||
|
||||
void Notify();
|
||||
void LoggedIn(sp_error error);
|
||||
|
||||
SpotifyClient* client_;
|
||||
|
||||
sp_session_config spotify_config_;
|
||||
sp_session_callbacks spotify_callbacks_;
|
||||
sp_session* session_;
|
||||
|
||||
QTimer* events_timer_;
|
||||
};
|
||||
|
||||
#endif // SPOTIFY_H
|
@ -15,76 +15,210 @@
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "messages.pb.h"
|
||||
#include "spotifyclient.h"
|
||||
#include "spotifykey.h"
|
||||
#include "spotifymessages.pb.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QHostAddress>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include <boost/scoped_array.hpp>
|
||||
#include <QTimer>
|
||||
|
||||
SpotifyClient::SpotifyClient(QObject* parent)
|
||||
: QObject(parent),
|
||||
socket_(new QTcpSocket(this))
|
||||
{
|
||||
socket_(new QTcpSocket(this)),
|
||||
session_(NULL),
|
||||
events_timer_(new QTimer(this)) {
|
||||
memset(&spotify_callbacks_, 0, sizeof(spotify_callbacks_));
|
||||
memset(&spotify_config_, 0, sizeof(spotify_config_));
|
||||
memset(&playlistcontainer_callbacks_, 0, sizeof(playlistcontainer_callbacks_));
|
||||
|
||||
spotify_callbacks_.logged_in = &LoggedInCallback;
|
||||
spotify_callbacks_.notify_main_thread = &NotifyMainThreadCallback;
|
||||
spotify_callbacks_.log_message = &LogMessageCallback;
|
||||
|
||||
playlistcontainer_callbacks_.container_loaded = &PlaylistContainerLoadedCallback;
|
||||
playlistcontainer_callbacks_.playlist_added = &PlaylistAddedCallback;
|
||||
playlistcontainer_callbacks_.playlist_moved = &PlaylistMovedCallback;
|
||||
playlistcontainer_callbacks_.playlist_removed = &PlaylistRemovedCallback;
|
||||
|
||||
spotify_config_.api_version = SPOTIFY_API_VERSION; // From libspotify/api.h
|
||||
spotify_config_.cache_location = strdup(QDir::tempPath().toLocal8Bit().constData());
|
||||
spotify_config_.settings_location = strdup(QDir::tempPath().toLocal8Bit().constData());
|
||||
spotify_config_.application_key = g_appkey;
|
||||
spotify_config_.application_key_size = g_appkey_size;
|
||||
spotify_config_.callbacks = &spotify_callbacks_;
|
||||
spotify_config_.userdata = this;
|
||||
spotify_config_.user_agent = "Clementine Player";
|
||||
|
||||
events_timer_->setSingleShot(true);
|
||||
connect(events_timer_, SIGNAL(timeout()), SLOT(ProcessEvents()));
|
||||
|
||||
connect(socket_, SIGNAL(readyRead()), SLOT(SocketReadyRead()));
|
||||
}
|
||||
|
||||
SpotifyClient::~SpotifyClient() {
|
||||
if (session_) {
|
||||
sp_session_release(session_);
|
||||
}
|
||||
|
||||
free(const_cast<char*>(spotify_config_.cache_location));
|
||||
free(const_cast<char*>(spotify_config_.settings_location));
|
||||
}
|
||||
|
||||
void SpotifyClient::Init(quint16 port) {
|
||||
qLog(Debug) << "port" << port;
|
||||
qLog(Debug) << "connecting to port" << port;
|
||||
|
||||
socket_->connectToHost(QHostAddress::LocalHost, port);
|
||||
}
|
||||
|
||||
void SpotifyClient::SocketReadyRead() {
|
||||
QDataStream s(socket_);
|
||||
void SpotifyClient::LoggedInCallback(sp_session* session, sp_error error) {
|
||||
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
|
||||
const bool success = error == SP_ERROR_OK;
|
||||
|
||||
quint32 length = 0;
|
||||
s >> length;
|
||||
if (!success) {
|
||||
qLog(Warning) << "Failed to login" << sp_error_message(error);
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
me->SendLoginCompleted(success, sp_error_message(error));
|
||||
|
||||
if (success) {
|
||||
sp_playlistcontainer_add_callbacks(
|
||||
sp_session_playlistcontainer(session),
|
||||
&me->playlistcontainer_callbacks_, me);
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyClient::NotifyMainThreadCallback(sp_session* session) {
|
||||
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
|
||||
QMetaObject::invokeMethod(me, "ProcessEvents", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void SpotifyClient::ProcessEvents() {
|
||||
int next_timeout_ms;
|
||||
sp_session_process_events(session_, &next_timeout_ms);
|
||||
events_timer_->start(next_timeout_ms);
|
||||
}
|
||||
|
||||
void SpotifyClient::LogMessageCallback(sp_session* session, const char* data) {
|
||||
qLog(Debug) << "libspotify:" << data;
|
||||
}
|
||||
|
||||
void SpotifyClient::Search(const QString& query) {
|
||||
sp_search_create(
|
||||
session_,
|
||||
query.toUtf8().constData(),
|
||||
0, // track offset
|
||||
10, // track count
|
||||
0, // album offset
|
||||
10, // album count
|
||||
0, // artist offset
|
||||
10, // artist count
|
||||
&SearchCompleteCallback,
|
||||
this);
|
||||
}
|
||||
|
||||
void SpotifyClient::SearchCompleteCallback(sp_search* result, void* userdata) {
|
||||
sp_error error = sp_search_error(result);
|
||||
if (error != SP_ERROR_OK) {
|
||||
qLog(Warning) << "Search failed";
|
||||
sp_search_release(result);
|
||||
return;
|
||||
}
|
||||
|
||||
boost::scoped_array<char> data(new char[length]);
|
||||
s.readRawData(data.get(), length);
|
||||
int artists = sp_search_num_artists(result);
|
||||
for (int i = 0; i < artists; ++i) {
|
||||
sp_artist* artist = sp_search_artist(result, i);
|
||||
qLog(Debug) << "Found artist:" << sp_artist_name(artist);
|
||||
}
|
||||
|
||||
RequestMessage message;
|
||||
if (!message.ParseFromArray(data.get(), length)) {
|
||||
qLog(Error) << "Malformed protobuf message";
|
||||
sp_search_release(result);
|
||||
}
|
||||
|
||||
void SpotifyClient::SocketReadyRead() {
|
||||
protobuf::SpotifyMessage message;
|
||||
if (!ReadMessage(socket_, &message)) {
|
||||
socket_->deleteLater();
|
||||
socket_ = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Debug) << message.DebugString().c_str();
|
||||
|
||||
if (message.has_login_request()) {
|
||||
const LoginRequest& r = message.login_request();
|
||||
emit Login(QString::fromUtf8(r.username().data(), r.username().length()),
|
||||
QString::fromUtf8(r.password().data(), r.password().length()));
|
||||
const protobuf::LoginRequest& r = message.login_request();
|
||||
Login(QStringFromStdString(r.username()), QStringFromStdString(r.password()));
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyClient::SendMessage(const ResponseMessage& message) {
|
||||
qLog(Debug) << message.DebugString().c_str();
|
||||
void SpotifyClient::Login(const QString& username, const QString& password) {
|
||||
sp_error error = sp_session_create(&spotify_config_, &session_);
|
||||
if (error != SP_ERROR_OK) {
|
||||
qLog(Warning) << "Failed to create session" << sp_error_message(error);
|
||||
SendLoginCompleted(false, sp_error_message(error));
|
||||
return;
|
||||
}
|
||||
|
||||
std::string data(message.SerializeAsString());
|
||||
|
||||
QDataStream s(socket_);
|
||||
s << quint32(data.length());
|
||||
s.writeRawData(data.data(), data.length());
|
||||
sp_session_login(session_, username.toUtf8().constData(), password.toUtf8().constData());
|
||||
}
|
||||
|
||||
void SpotifyClient::LoginCompleted(bool success, const QString& error) {
|
||||
const QByteArray error_bytes(error.toUtf8());
|
||||
void SpotifyClient::SendLoginCompleted(bool success, const QString& error) {
|
||||
protobuf::SpotifyMessage message;
|
||||
|
||||
ResponseMessage message;
|
||||
|
||||
LoginResponse* response = message.mutable_login_response();
|
||||
protobuf::LoginResponse* response = message.mutable_login_response();
|
||||
response->set_success(success);
|
||||
response->set_error(error_bytes.constData(), error_bytes.length());
|
||||
response->set_error(DataCommaSizeFromQString(error));
|
||||
|
||||
SendMessage(message);
|
||||
SendMessage(socket_, message);
|
||||
}
|
||||
|
||||
void SpotifyClient::PlaylistContainerLoadedCallback(sp_playlistcontainer* pc, void* userdata) {
|
||||
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
|
||||
me->GetPlaylists();
|
||||
}
|
||||
|
||||
void SpotifyClient::PlaylistAddedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, void* userdata) {
|
||||
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
|
||||
me->GetPlaylists();
|
||||
}
|
||||
|
||||
void SpotifyClient::PlaylistMovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, int new_position, void* userdata) {
|
||||
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
|
||||
me->GetPlaylists();
|
||||
}
|
||||
|
||||
void SpotifyClient::PlaylistRemovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist, int position, void* userdata) {
|
||||
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(userdata);
|
||||
me->GetPlaylists();
|
||||
}
|
||||
|
||||
void SpotifyClient::GetPlaylists() {
|
||||
protobuf::SpotifyMessage message;
|
||||
protobuf::Playlists* response = message.mutable_playlists_updated();
|
||||
|
||||
sp_playlistcontainer* container = sp_session_playlistcontainer(session_);
|
||||
if (!container) {
|
||||
qLog(Warning) << "sp_session_playlistcontainer returned NULL";
|
||||
return;
|
||||
}
|
||||
|
||||
const int count = sp_playlistcontainer_num_playlists(container);
|
||||
qLog(Debug) << "Playlist container has" << count << "playlists";
|
||||
|
||||
for (int i=0 ; i<count ; ++i) {
|
||||
const int type = sp_playlistcontainer_playlist_type(container, i);
|
||||
sp_playlist* playlist = sp_playlistcontainer_playlist(container, i);
|
||||
|
||||
qLog(Debug) << "Got playlist" << i << type << sp_playlist_name(playlist);
|
||||
|
||||
if (type != SP_PLAYLIST_TYPE_PLAYLIST) {
|
||||
// Just ignore folders for now
|
||||
continue;
|
||||
}
|
||||
|
||||
protobuf::Playlists::Playlist* msg = response->add_playlist();
|
||||
msg->set_index(i);
|
||||
msg->set_name(sp_playlist_name(playlist));
|
||||
}
|
||||
|
||||
SendMessage(socket_, message);
|
||||
}
|
||||
|
@ -1,32 +1,65 @@
|
||||
#ifndef SPOTIFYCLIENT_H
|
||||
#define SPOTIFYCLIENT_H
|
||||
|
||||
#include "spotifymessageutils.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <libspotify/api.h>
|
||||
|
||||
class QTcpSocket;
|
||||
class QTimer;
|
||||
|
||||
class ResponseMessage;
|
||||
|
||||
class SpotifyClient : public QObject {
|
||||
class SpotifyClient : public QObject, protected SpotifyMessageUtils {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpotifyClient(QObject* parent = 0);
|
||||
~SpotifyClient();
|
||||
|
||||
void Init(quint16 port);
|
||||
|
||||
void LoginCompleted(bool success, const QString& error);
|
||||
|
||||
signals:
|
||||
void Login(const QString& username, const QString& password);
|
||||
|
||||
private slots:
|
||||
void SocketReadyRead();
|
||||
void ProcessEvents();
|
||||
|
||||
private:
|
||||
void SendMessage(const ResponseMessage& message);
|
||||
void SendLoginCompleted(bool success, const QString& error);
|
||||
|
||||
// Spotify session callbacks.
|
||||
static void LoggedInCallback(sp_session* session, sp_error error);
|
||||
static void NotifyMainThreadCallback(sp_session* session);
|
||||
static void LogMessageCallback(sp_session* session, const char* data);
|
||||
static void SearchCompleteCallback(sp_search* result, void* userdata);
|
||||
|
||||
// Spotify playlist container callbacks.
|
||||
static void PlaylistAddedCallback(
|
||||
sp_playlistcontainer* pc, sp_playlist* playlist,
|
||||
int position, void* userdata);
|
||||
static void PlaylistRemovedCallback(
|
||||
sp_playlistcontainer* pc, sp_playlist* playlist,
|
||||
int position, void* userdata);
|
||||
static void PlaylistMovedCallback(
|
||||
sp_playlistcontainer* pc, sp_playlist* playlist,
|
||||
int position, int new_position, void* userdata);
|
||||
static void PlaylistContainerLoadedCallback(
|
||||
sp_playlistcontainer* pc, void* userdata);
|
||||
|
||||
// Request handlers.
|
||||
void Login(const QString& username, const QString& password);
|
||||
void GetPlaylists();
|
||||
void Search(const QString& query);
|
||||
|
||||
QTcpSocket* socket_;
|
||||
|
||||
sp_session_config spotify_config_;
|
||||
sp_session_callbacks spotify_callbacks_;
|
||||
sp_playlistcontainer_callbacks playlistcontainer_callbacks_;
|
||||
sp_session* session_;
|
||||
|
||||
QTimer* events_timer_;
|
||||
};
|
||||
|
||||
#endif // SPOTIFYCLIENT_H
|
||||
|
26
spotifyblob/spotifymessages.proto
Normal file
26
spotifyblob/spotifymessages.proto
Normal file
@ -0,0 +1,26 @@
|
||||
package protobuf;
|
||||
|
||||
message LoginRequest {
|
||||
required string username = 1;
|
||||
required string password = 2;
|
||||
}
|
||||
|
||||
message LoginResponse {
|
||||
required bool success = 1;
|
||||
required string error = 2;
|
||||
}
|
||||
|
||||
message Playlists {
|
||||
message Playlist {
|
||||
required int32 index = 1;
|
||||
required string name = 2;
|
||||
}
|
||||
|
||||
repeated Playlist playlist = 1;
|
||||
}
|
||||
|
||||
message SpotifyMessage {
|
||||
optional LoginRequest login_request = 1;
|
||||
optional LoginResponse login_response = 2;
|
||||
optional Playlists playlists_updated = 3;
|
||||
}
|
40
spotifyblob/spotifymessageutils.cpp
Normal file
40
spotifyblob/spotifymessageutils.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include "spotifymessages.pb.h"
|
||||
#include "spotifymessageutils.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <boost/scoped_array.hpp>
|
||||
|
||||
bool SpotifyMessageUtils::ReadMessage(QIODevice* device,
|
||||
protobuf::SpotifyMessage* message) {
|
||||
QDataStream s(device);
|
||||
|
||||
quint32 length = 0;
|
||||
s >> length;
|
||||
|
||||
if (length == 0) {
|
||||
qLog(Error) << "Zero length protobuf message body";
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::scoped_array<char> data(new char[length]);
|
||||
s.readRawData(data.get(), length);
|
||||
|
||||
if (!message->ParseFromArray(data.get(), length)) {
|
||||
qLog(Error) << "Malformed protobuf message";
|
||||
return false;
|
||||
}
|
||||
|
||||
qLog(Debug) << message->DebugString().c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpotifyMessageUtils::SendMessage(QIODevice* device,
|
||||
const protobuf::SpotifyMessage& message) {
|
||||
qLog(Debug) << message.DebugString().c_str();
|
||||
|
||||
std::string data(message.SerializeAsString());
|
||||
|
||||
QDataStream s(device);
|
||||
s << quint32(data.length());
|
||||
s.writeRawData(data.data(), data.length());
|
||||
}
|
27
spotifyblob/spotifymessageutils.h
Normal file
27
spotifyblob/spotifymessageutils.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef SPOTIFYMESSAGEUTILS_H
|
||||
#define SPOTIFYMESSAGEUTILS_H
|
||||
|
||||
namespace protobuf {
|
||||
class SpotifyMessage;
|
||||
}
|
||||
|
||||
class QIODevice;
|
||||
|
||||
#define QStringFromStdString(x) \
|
||||
QString::fromUtf8(x.data(), x.size())
|
||||
#define DataCommaSizeFromQString(x) \
|
||||
x.toUtf8().constData(), x.toUtf8().length()
|
||||
|
||||
|
||||
class SpotifyMessageUtils {
|
||||
public:
|
||||
virtual ~SpotifyMessageUtils() {}
|
||||
|
||||
protected:
|
||||
SpotifyMessageUtils() {}
|
||||
|
||||
bool ReadMessage(QIODevice* device, protobuf::SpotifyMessage* message);
|
||||
void SendMessage(QIODevice* device, const protobuf::SpotifyMessage& message);
|
||||
};
|
||||
|
||||
#endif // SPOTIFYMESSAGEUTILS_H
|
@ -72,6 +72,8 @@ public:
|
||||
// a pointer to a RadioService. Services should not set this field
|
||||
// themselves.
|
||||
Role_Service,
|
||||
|
||||
RoleCount
|
||||
};
|
||||
|
||||
enum Type {
|
||||
|
@ -29,6 +29,7 @@ class SpotifyService;
|
||||
|
||||
class SpotifyConfig : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SpotifyConfig(QWidget* parent = 0);
|
||||
~SpotifyConfig();
|
||||
|
@ -18,17 +18,16 @@
|
||||
#include "spotifyserver.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "spotifyblob/messages.pb.h"
|
||||
#include "spotifyblob/spotifymessages.pb.h"
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include <boost/scoped_array.hpp>
|
||||
|
||||
SpotifyServer::SpotifyServer(QObject* parent)
|
||||
: QObject(parent),
|
||||
server_(new QTcpServer(this)),
|
||||
protocol_socket_(NULL)
|
||||
protocol_socket_(NULL),
|
||||
logged_in_(false)
|
||||
{
|
||||
connect(server_, SIGNAL(newConnection()), SLOT(NewConnection()));
|
||||
}
|
||||
@ -49,60 +48,60 @@ void SpotifyServer::NewConnection() {
|
||||
|
||||
qLog(Info) << "Connection from port" << protocol_socket_->peerPort();
|
||||
|
||||
emit ClientConnected();
|
||||
// Send any login messages that were queued before the client connected
|
||||
foreach (const protobuf::SpotifyMessage& message, queued_login_messages_) {
|
||||
SendMessage(message);
|
||||
}
|
||||
queued_login_messages_.clear();
|
||||
}
|
||||
|
||||
void SpotifyServer::SendMessage(const protobuf::SpotifyMessage& message) {
|
||||
const bool is_login_message = message.has_login_request();
|
||||
|
||||
QList<protobuf::SpotifyMessage>* queue =
|
||||
is_login_message ? &queued_login_messages_ : &queued_messages_;
|
||||
|
||||
if (!protocol_socket_ || (!is_login_message && !logged_in_)) {
|
||||
queue->append(message);
|
||||
} else {
|
||||
SpotifyMessageUtils::SendMessage(protocol_socket_, message);
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyServer::Login(const QString& username, const QString& password) {
|
||||
const QByteArray username_bytes(username.toUtf8());
|
||||
const QByteArray password_bytes(password.toUtf8());
|
||||
protobuf::SpotifyMessage message;
|
||||
|
||||
RequestMessage message;
|
||||
|
||||
LoginRequest* request = message.mutable_login_request();
|
||||
request->set_username(username_bytes.constData(), username_bytes.length());
|
||||
request->set_password(password_bytes.constData(), password_bytes.length());
|
||||
protobuf::LoginRequest* request = message.mutable_login_request();
|
||||
request->set_username(DataCommaSizeFromQString(username));
|
||||
request->set_password(DataCommaSizeFromQString(password));
|
||||
|
||||
SendMessage(message);
|
||||
}
|
||||
|
||||
void SpotifyServer::SendMessage(const RequestMessage& message) {
|
||||
qLog(Debug) << message.DebugString().c_str();
|
||||
|
||||
std::string data(message.SerializeAsString());
|
||||
|
||||
QDataStream s(protocol_socket_);
|
||||
s << quint32(data.length());
|
||||
s.writeRawData(data.data(), data.length());
|
||||
}
|
||||
|
||||
void SpotifyServer::ProtocolSocketReadyRead() {
|
||||
QDataStream s(protocol_socket_);
|
||||
|
||||
quint32 length = 0;
|
||||
s >> length;
|
||||
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boost::scoped_array<char> data(new char[length]);
|
||||
s.readRawData(data.get(), length);
|
||||
|
||||
ResponseMessage message;
|
||||
if (!message.ParseFromArray(data.get(), length)) {
|
||||
qLog(Error) << "Malformed protobuf message";
|
||||
protobuf::SpotifyMessage message;
|
||||
if (!ReadMessage(protocol_socket_, &message)) {
|
||||
protocol_socket_->deleteLater();
|
||||
protocol_socket_ = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Debug) << message.DebugString().c_str();
|
||||
|
||||
if (message.has_login_response()) {
|
||||
const LoginResponse& response = message.login_response();
|
||||
const protobuf::LoginResponse& response = message.login_response();
|
||||
logged_in_ = response.success();
|
||||
|
||||
if (!response.success()) {
|
||||
qLog(Info) << QString::fromUtf8(response.error().data(), response.error().size());
|
||||
qLog(Info) << QStringFromStdString(response.error());
|
||||
} else {
|
||||
// Send any messages that were queued before the client logged in
|
||||
foreach (const protobuf::SpotifyMessage& message, queued_messages_) {
|
||||
SendMessage(message);
|
||||
}
|
||||
queued_messages_.clear();
|
||||
}
|
||||
|
||||
emit LoginCompleted(response.success());
|
||||
} else if (message.has_playlists_updated()) {
|
||||
emit PlaylistsUpdated(message.playlists_updated());
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,15 @@
|
||||
#ifndef SPOTIFYSERVER_H
|
||||
#define SPOTIFYSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include "spotifyblob/spotifymessages.pb.h"
|
||||
#include "spotifyblob/spotifymessageutils.h"
|
||||
|
||||
class RequestMessage;
|
||||
#include <QObject>
|
||||
|
||||
class QTcpServer;
|
||||
class QTcpSocket;
|
||||
|
||||
class SpotifyServer : public QObject {
|
||||
class SpotifyServer : public QObject, protected SpotifyMessageUtils {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@ -37,18 +38,22 @@ public:
|
||||
int server_port() const;
|
||||
|
||||
signals:
|
||||
void ClientConnected();
|
||||
void LoginCompleted(bool success);
|
||||
void PlaylistsUpdated(const protobuf::Playlists& playlists);
|
||||
|
||||
private slots:
|
||||
void NewConnection();
|
||||
void ProtocolSocketReadyRead();
|
||||
|
||||
private:
|
||||
void SendMessage(const RequestMessage& message);
|
||||
void SendMessage(const protobuf::SpotifyMessage& message);
|
||||
|
||||
QTcpServer* server_;
|
||||
QTcpSocket* protocol_socket_;
|
||||
bool logged_in_;
|
||||
|
||||
QList<protobuf::SpotifyMessage> queued_login_messages_;
|
||||
QList<protobuf::SpotifyMessage> queued_messages_;
|
||||
};
|
||||
|
||||
#endif // SPOTIFYSERVER_H
|
||||
|
@ -1,10 +1,13 @@
|
||||
#include "core/logging.h"
|
||||
#include "radiomodel.h"
|
||||
#include "spotifyserver.h"
|
||||
#include "spotifyservice.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
|
||||
const char* SpotifyService::kServiceName = "Spotify";
|
||||
const char* SpotifyService::kSettingsGroup = "Spotify";
|
||||
@ -12,7 +15,11 @@ const char* SpotifyService::kSettingsGroup = "Spotify";
|
||||
SpotifyService::SpotifyService(RadioModel* parent)
|
||||
: RadioService(kServiceName, parent),
|
||||
server_(NULL),
|
||||
blob_process_(NULL) {
|
||||
blob_process_(NULL),
|
||||
root_(NULL),
|
||||
starred_(NULL),
|
||||
inbox_(NULL),
|
||||
login_task_id_(0) {
|
||||
blob_path_ = QCoreApplication::applicationFilePath() + "-spotifyblob";
|
||||
}
|
||||
|
||||
@ -20,12 +27,21 @@ SpotifyService::~SpotifyService() {
|
||||
}
|
||||
|
||||
QStandardItem* SpotifyService::CreateRootItem() {
|
||||
QStandardItem* item = new QStandardItem(QIcon(":icons/svg/spotify.svg"), kServiceName);
|
||||
item->setData(true, RadioModel::Role_CanLazyLoad);
|
||||
return item;
|
||||
root_ = new QStandardItem(QIcon(":icons/svg/spotify.svg"), kServiceName);
|
||||
root_->setData(true, RadioModel::Role_CanLazyLoad);
|
||||
return root_;
|
||||
}
|
||||
|
||||
void SpotifyService::LazyPopulate(QStandardItem* parent) {
|
||||
void SpotifyService::LazyPopulate(QStandardItem* item) {
|
||||
switch (item->data(RadioModel::Role_Type).toInt()) {
|
||||
case RadioModel::Type_Service:
|
||||
EnsureServerCreated();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -34,39 +50,95 @@ QModelIndex SpotifyService::GetCurrentIndex() {
|
||||
}
|
||||
|
||||
void SpotifyService::Login(const QString& username, const QString& password) {
|
||||
qLog(Debug) << Q_FUNC_INFO;
|
||||
delete server_;
|
||||
delete blob_process_;
|
||||
server_ = NULL;
|
||||
blob_process_ = NULL;
|
||||
|
||||
EnsureServerCreated(username, password);
|
||||
}
|
||||
|
||||
void SpotifyService::LoginCompleted(bool success) {
|
||||
if (login_task_id_) {
|
||||
model()->task_manager()->SetTaskFinished(login_task_id_);
|
||||
login_task_id_ = 0;
|
||||
}
|
||||
|
||||
emit LoginFinished(success);
|
||||
}
|
||||
|
||||
void SpotifyService::BlobProcessError(QProcess::ProcessError error) {
|
||||
qLog(Error) << "Spotify blob process failed:" << error;
|
||||
}
|
||||
|
||||
void SpotifyService::EnsureServerCreated(const QString& username,
|
||||
const QString& password) {
|
||||
if (server_) {
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Debug) << Q_FUNC_INFO;
|
||||
|
||||
server_ = new SpotifyServer(this);
|
||||
blob_process_ = new QProcess(this);
|
||||
blob_process_->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
|
||||
connect(server_, SIGNAL(ClientConnected()), SLOT(ClientConnected()));
|
||||
connect(server_, SIGNAL(LoginCompleted(bool)), SLOT(LoginCompleted(bool)));
|
||||
connect(server_, SIGNAL(PlaylistsUpdated(protobuf::Playlists)),
|
||||
SLOT(PlaylistsUpdated(protobuf::Playlists)));
|
||||
|
||||
connect(blob_process_,
|
||||
SIGNAL(error(QProcess::ProcessError)),
|
||||
SLOT(BlobProcessError(QProcess::ProcessError)));
|
||||
|
||||
pending_username_ = username;
|
||||
pending_password_ = password;
|
||||
|
||||
server_->Init();
|
||||
blob_process_->start(
|
||||
blob_path_, QStringList() << QString::number(server_->server_port()));
|
||||
|
||||
login_task_id_ = model()->task_manager()->StartTask(tr("Connecting to Spotify"));
|
||||
|
||||
if (username.isEmpty()) {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
server_->Login(s.value("username").toString(), s.value("password").toString());
|
||||
} else {
|
||||
server_->Login(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyService::ClientConnected() {
|
||||
qLog(Debug) << "ClientConnected";
|
||||
void SpotifyService::PlaylistsUpdated(const protobuf::Playlists& response) {
|
||||
if (login_task_id_) {
|
||||
model()->task_manager()->SetTaskFinished(login_task_id_);
|
||||
login_task_id_ = 0;
|
||||
}
|
||||
|
||||
server_->Login(pending_username_, pending_password_);
|
||||
}
|
||||
// Create starred and inbox playlists if they're not here already
|
||||
if (!starred_) {
|
||||
starred_ = new QStandardItem(QIcon(":/star-on.png"), tr("Starred"));
|
||||
starred_->setData(Type_StarredPlaylist, RadioModel::Role_Type);
|
||||
starred_->setData(true, RadioModel::Role_CanLazyLoad);
|
||||
|
||||
void SpotifyService::LoginCompleted(bool success) {
|
||||
emit LoginFinished(success);
|
||||
}
|
||||
inbox_ = new QStandardItem(IconLoader::Load("mail-message"), tr("Inbox"));
|
||||
inbox_->setData(Type_InboxPlaylist, RadioModel::Role_Type);
|
||||
inbox_->setData(true, RadioModel::Role_CanLazyLoad);
|
||||
|
||||
void SpotifyService::BlobProcessError(QProcess::ProcessError error) {
|
||||
qLog(Error) << "Failed to start blob process:" << error;
|
||||
root_->appendRow(starred_);
|
||||
root_->appendRow(inbox_);
|
||||
}
|
||||
|
||||
// Remove and recreate the other playlists
|
||||
qDeleteAll(playlists_);
|
||||
playlists_.clear();
|
||||
|
||||
for (int i=0 ; i<response.playlist_size() ; ++i) {
|
||||
const protobuf::Playlists::Playlist& msg = response.playlist(i);
|
||||
|
||||
QStandardItem* item = new QStandardItem(QStringFromStdString(msg.name()));
|
||||
item->setData(Type_Playlist, RadioModel::Role_Type);
|
||||
item->setData(true, RadioModel::Role_CanLazyLoad);
|
||||
item->setData(msg.index(), Role_PlaylistIndex);
|
||||
|
||||
root_->appendRow(item);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
#ifndef SPOTIFYSERVICE_H
|
||||
#define SPOTIFYSERVICE_H
|
||||
|
||||
#include "radiomodel.h"
|
||||
#include "radioservice.h"
|
||||
#include "spotifyblob/spotifymessages.pb.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
@ -12,14 +14,23 @@ class SpotifyService : public RadioService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SpotifyService(RadioModel* parent);
|
||||
virtual ~SpotifyService();
|
||||
SpotifyService(RadioModel* parent);
|
||||
~SpotifyService();
|
||||
|
||||
enum Type {
|
||||
Type_StarredPlaylist = RadioModel::TypeCount,
|
||||
Type_InboxPlaylist,
|
||||
Type_Playlist,
|
||||
};
|
||||
|
||||
enum Role {
|
||||
Role_PlaylistIndex = RadioModel::RoleCount,
|
||||
};
|
||||
|
||||
virtual QStandardItem* CreateRootItem();
|
||||
virtual void LazyPopulate(QStandardItem* parent);
|
||||
|
||||
void Login(const QString& username, const QString& password);
|
||||
void Search(const QString& query);
|
||||
|
||||
static const char* kServiceName;
|
||||
static const char* kSettingsGroup;
|
||||
@ -30,10 +41,14 @@ signals:
|
||||
protected:
|
||||
virtual QModelIndex GetCurrentIndex();
|
||||
|
||||
private:
|
||||
void EnsureServerCreated(const QString& username = QString(),
|
||||
const QString& password = QString());
|
||||
|
||||
private slots:
|
||||
void ClientConnected();
|
||||
void LoginCompleted(bool success);
|
||||
void BlobProcessError(QProcess::ProcessError error);
|
||||
void LoginCompleted(bool success);
|
||||
void PlaylistsUpdated(const protobuf::Playlists& response);
|
||||
|
||||
private:
|
||||
SpotifyServer* server_;
|
||||
@ -41,8 +56,12 @@ private:
|
||||
QString blob_path_;
|
||||
QProcess* blob_process_;
|
||||
|
||||
QString pending_username_;
|
||||
QString pending_password_;
|
||||
QStandardItem* root_;
|
||||
QStandardItem* starred_;
|
||||
QStandardItem* inbox_;
|
||||
QList<QStandardItem*> playlists_;
|
||||
|
||||
int login_task_id_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user