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_disabled.png</file>
|
||||||
<file>last.fm/as_light.png</file>
|
<file>last.fm/as_light.png</file>
|
||||||
<file>icons/32x32/tools-wizard.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>
|
</qresource>
|
||||||
</RCC>
|
</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(EXECUTABLE_OUTPUT_PATH ..)
|
||||||
|
|
||||||
|
|
||||||
set(SOURCES
|
set(COMMON_SOURCES
|
||||||
main.cpp
|
spotifymessageutils.cpp
|
||||||
spotifyblob.cpp
|
|
||||||
spotifyclient.cpp
|
|
||||||
|
|
||||||
../src/core/logging.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(COMMON_MESSAGES
|
||||||
spotifyblob.h
|
spotifymessages.proto
|
||||||
spotifyclient.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(MESSAGES
|
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${COMMON_MESSAGES})
|
||||||
messages.proto
|
|
||||||
)
|
|
||||||
|
|
||||||
qt4_wrap_cpp(MOC ${HEADERS})
|
|
||||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
|
||||||
|
|
||||||
|
|
||||||
add_library(clementine-spotifyblob-messages STATIC
|
add_library(clementine-spotifyblob-messages STATIC
|
||||||
|
${COMMON_SOURCES}
|
||||||
${PROTO_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
|
add_executable(clementine-spotifyblob
|
||||||
${SOURCES}
|
${SOURCES}
|
||||||
${MOC}
|
${MOC}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
#include "spotifyblob.h"
|
|
||||||
#include "spotifyclient.h"
|
#include "spotifyclient.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
|
||||||
@ -17,8 +16,6 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SpotifyClient client;
|
SpotifyClient client;
|
||||||
SpotifyBlob blob(&client);
|
|
||||||
|
|
||||||
client.Init(arguments[1].toInt());
|
client.Init(arguments[1].toInt());
|
||||||
|
|
||||||
return a.exec();
|
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/>.
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "messages.pb.h"
|
|
||||||
#include "spotifyclient.h"
|
#include "spotifyclient.h"
|
||||||
|
#include "spotifykey.h"
|
||||||
|
#include "spotifymessages.pb.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
|
#include <QTimer>
|
||||||
#include <boost/scoped_array.hpp>
|
|
||||||
|
|
||||||
SpotifyClient::SpotifyClient(QObject* parent)
|
SpotifyClient::SpotifyClient(QObject* parent)
|
||||||
: 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()));
|
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) {
|
void SpotifyClient::Init(quint16 port) {
|
||||||
qLog(Debug) << "port" << port;
|
qLog(Debug) << "connecting to port" << port;
|
||||||
|
|
||||||
socket_->connectToHost(QHostAddress::LocalHost, port);
|
socket_->connectToHost(QHostAddress::LocalHost, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpotifyClient::SocketReadyRead() {
|
void SpotifyClient::LoggedInCallback(sp_session* session, sp_error error) {
|
||||||
QDataStream s(socket_);
|
SpotifyClient* me = reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
|
||||||
|
const bool success = error == SP_ERROR_OK;
|
||||||
|
|
||||||
quint32 length = 0;
|
if (!success) {
|
||||||
s >> length;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::scoped_array<char> data(new char[length]);
|
int artists = sp_search_num_artists(result);
|
||||||
s.readRawData(data.get(), length);
|
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;
|
sp_search_release(result);
|
||||||
if (!message.ParseFromArray(data.get(), length)) {
|
}
|
||||||
qLog(Error) << "Malformed protobuf message";
|
|
||||||
|
void SpotifyClient::SocketReadyRead() {
|
||||||
|
protobuf::SpotifyMessage message;
|
||||||
|
if (!ReadMessage(socket_, &message)) {
|
||||||
socket_->deleteLater();
|
socket_->deleteLater();
|
||||||
socket_ = NULL;
|
socket_ = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qLog(Debug) << message.DebugString().c_str();
|
|
||||||
|
|
||||||
if (message.has_login_request()) {
|
if (message.has_login_request()) {
|
||||||
const LoginRequest& r = message.login_request();
|
const protobuf::LoginRequest& r = message.login_request();
|
||||||
emit Login(QString::fromUtf8(r.username().data(), r.username().length()),
|
Login(QStringFromStdString(r.username()), QStringFromStdString(r.password()));
|
||||||
QString::fromUtf8(r.password().data(), r.password().length()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpotifyClient::SendMessage(const ResponseMessage& message) {
|
void SpotifyClient::Login(const QString& username, const QString& password) {
|
||||||
qLog(Debug) << message.DebugString().c_str();
|
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());
|
sp_session_login(session_, username.toUtf8().constData(), password.toUtf8().constData());
|
||||||
|
|
||||||
QDataStream s(socket_);
|
|
||||||
s << quint32(data.length());
|
|
||||||
s.writeRawData(data.data(), data.length());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpotifyClient::LoginCompleted(bool success, const QString& error) {
|
void SpotifyClient::SendLoginCompleted(bool success, const QString& error) {
|
||||||
const QByteArray error_bytes(error.toUtf8());
|
protobuf::SpotifyMessage message;
|
||||||
|
|
||||||
ResponseMessage message;
|
protobuf::LoginResponse* response = message.mutable_login_response();
|
||||||
|
|
||||||
LoginResponse* response = message.mutable_login_response();
|
|
||||||
response->set_success(success);
|
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
|
#ifndef SPOTIFYCLIENT_H
|
||||||
#define SPOTIFYCLIENT_H
|
#define SPOTIFYCLIENT_H
|
||||||
|
|
||||||
|
#include "spotifymessageutils.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <libspotify/api.h>
|
||||||
|
|
||||||
class QTcpSocket;
|
class QTcpSocket;
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
class ResponseMessage;
|
class ResponseMessage;
|
||||||
|
|
||||||
class SpotifyClient : public QObject {
|
class SpotifyClient : public QObject, protected SpotifyMessageUtils {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SpotifyClient(QObject* parent = 0);
|
SpotifyClient(QObject* parent = 0);
|
||||||
|
~SpotifyClient();
|
||||||
|
|
||||||
void Init(quint16 port);
|
void Init(quint16 port);
|
||||||
|
|
||||||
void LoginCompleted(bool success, const QString& error);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void Login(const QString& username, const QString& password);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void SocketReadyRead();
|
void SocketReadyRead();
|
||||||
|
void ProcessEvents();
|
||||||
|
|
||||||
private:
|
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_;
|
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
|
#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
|
// a pointer to a RadioService. Services should not set this field
|
||||||
// themselves.
|
// themselves.
|
||||||
Role_Service,
|
Role_Service,
|
||||||
|
|
||||||
|
RoleCount
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
|
@ -29,6 +29,7 @@ class SpotifyService;
|
|||||||
|
|
||||||
class SpotifyConfig : public QWidget {
|
class SpotifyConfig : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SpotifyConfig(QWidget* parent = 0);
|
SpotifyConfig(QWidget* parent = 0);
|
||||||
~SpotifyConfig();
|
~SpotifyConfig();
|
||||||
|
@ -18,17 +18,16 @@
|
|||||||
#include "spotifyserver.h"
|
#include "spotifyserver.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
|
||||||
#include "spotifyblob/messages.pb.h"
|
#include "spotifyblob/spotifymessages.pb.h"
|
||||||
|
|
||||||
#include <QTcpServer>
|
#include <QTcpServer>
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
|
|
||||||
#include <boost/scoped_array.hpp>
|
|
||||||
|
|
||||||
SpotifyServer::SpotifyServer(QObject* parent)
|
SpotifyServer::SpotifyServer(QObject* parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
server_(new QTcpServer(this)),
|
server_(new QTcpServer(this)),
|
||||||
protocol_socket_(NULL)
|
protocol_socket_(NULL),
|
||||||
|
logged_in_(false)
|
||||||
{
|
{
|
||||||
connect(server_, SIGNAL(newConnection()), SLOT(NewConnection()));
|
connect(server_, SIGNAL(newConnection()), SLOT(NewConnection()));
|
||||||
}
|
}
|
||||||
@ -49,60 +48,60 @@ void SpotifyServer::NewConnection() {
|
|||||||
|
|
||||||
qLog(Info) << "Connection from port" << protocol_socket_->peerPort();
|
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) {
|
void SpotifyServer::Login(const QString& username, const QString& password) {
|
||||||
const QByteArray username_bytes(username.toUtf8());
|
protobuf::SpotifyMessage message;
|
||||||
const QByteArray password_bytes(password.toUtf8());
|
|
||||||
|
|
||||||
RequestMessage message;
|
protobuf::LoginRequest* request = message.mutable_login_request();
|
||||||
|
request->set_username(DataCommaSizeFromQString(username));
|
||||||
LoginRequest* request = message.mutable_login_request();
|
request->set_password(DataCommaSizeFromQString(password));
|
||||||
request->set_username(username_bytes.constData(), username_bytes.length());
|
|
||||||
request->set_password(password_bytes.constData(), password_bytes.length());
|
|
||||||
|
|
||||||
SendMessage(message);
|
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() {
|
void SpotifyServer::ProtocolSocketReadyRead() {
|
||||||
QDataStream s(protocol_socket_);
|
protobuf::SpotifyMessage message;
|
||||||
|
if (!ReadMessage(protocol_socket_, &message)) {
|
||||||
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";
|
|
||||||
protocol_socket_->deleteLater();
|
protocol_socket_->deleteLater();
|
||||||
protocol_socket_ = NULL;
|
protocol_socket_ = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qLog(Debug) << message.DebugString().c_str();
|
|
||||||
|
|
||||||
if (message.has_login_response()) {
|
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()) {
|
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());
|
emit LoginCompleted(response.success());
|
||||||
|
} else if (message.has_playlists_updated()) {
|
||||||
|
emit PlaylistsUpdated(message.playlists_updated());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,15 @@
|
|||||||
#ifndef SPOTIFYSERVER_H
|
#ifndef SPOTIFYSERVER_H
|
||||||
#define SPOTIFYSERVER_H
|
#define SPOTIFYSERVER_H
|
||||||
|
|
||||||
#include <QObject>
|
#include "spotifyblob/spotifymessages.pb.h"
|
||||||
|
#include "spotifyblob/spotifymessageutils.h"
|
||||||
|
|
||||||
class RequestMessage;
|
#include <QObject>
|
||||||
|
|
||||||
class QTcpServer;
|
class QTcpServer;
|
||||||
class QTcpSocket;
|
class QTcpSocket;
|
||||||
|
|
||||||
class SpotifyServer : public QObject {
|
class SpotifyServer : public QObject, protected SpotifyMessageUtils {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -37,18 +38,22 @@ public:
|
|||||||
int server_port() const;
|
int server_port() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ClientConnected();
|
|
||||||
void LoginCompleted(bool success);
|
void LoginCompleted(bool success);
|
||||||
|
void PlaylistsUpdated(const protobuf::Playlists& playlists);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void NewConnection();
|
void NewConnection();
|
||||||
void ProtocolSocketReadyRead();
|
void ProtocolSocketReadyRead();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SendMessage(const RequestMessage& message);
|
void SendMessage(const protobuf::SpotifyMessage& message);
|
||||||
|
|
||||||
QTcpServer* server_;
|
QTcpServer* server_;
|
||||||
QTcpSocket* protocol_socket_;
|
QTcpSocket* protocol_socket_;
|
||||||
|
bool logged_in_;
|
||||||
|
|
||||||
|
QList<protobuf::SpotifyMessage> queued_login_messages_;
|
||||||
|
QList<protobuf::SpotifyMessage> queued_messages_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SPOTIFYSERVER_H
|
#endif // SPOTIFYSERVER_H
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
#include "core/logging.h"
|
|
||||||
#include "radiomodel.h"
|
#include "radiomodel.h"
|
||||||
#include "spotifyserver.h"
|
#include "spotifyserver.h"
|
||||||
#include "spotifyservice.h"
|
#include "spotifyservice.h"
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "core/taskmanager.h"
|
||||||
|
#include "ui/iconloader.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
const char* SpotifyService::kServiceName = "Spotify";
|
const char* SpotifyService::kServiceName = "Spotify";
|
||||||
const char* SpotifyService::kSettingsGroup = "Spotify";
|
const char* SpotifyService::kSettingsGroup = "Spotify";
|
||||||
@ -12,7 +15,11 @@ const char* SpotifyService::kSettingsGroup = "Spotify";
|
|||||||
SpotifyService::SpotifyService(RadioModel* parent)
|
SpotifyService::SpotifyService(RadioModel* parent)
|
||||||
: RadioService(kServiceName, parent),
|
: RadioService(kServiceName, parent),
|
||||||
server_(NULL),
|
server_(NULL),
|
||||||
blob_process_(NULL) {
|
blob_process_(NULL),
|
||||||
|
root_(NULL),
|
||||||
|
starred_(NULL),
|
||||||
|
inbox_(NULL),
|
||||||
|
login_task_id_(0) {
|
||||||
blob_path_ = QCoreApplication::applicationFilePath() + "-spotifyblob";
|
blob_path_ = QCoreApplication::applicationFilePath() + "-spotifyblob";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,12 +27,21 @@ SpotifyService::~SpotifyService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QStandardItem* SpotifyService::CreateRootItem() {
|
QStandardItem* SpotifyService::CreateRootItem() {
|
||||||
QStandardItem* item = new QStandardItem(QIcon(":icons/svg/spotify.svg"), kServiceName);
|
root_ = new QStandardItem(QIcon(":icons/svg/spotify.svg"), kServiceName);
|
||||||
item->setData(true, RadioModel::Role_CanLazyLoad);
|
root_->setData(true, RadioModel::Role_CanLazyLoad);
|
||||||
return item;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,39 +50,95 @@ QModelIndex SpotifyService::GetCurrentIndex() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpotifyService::Login(const QString& username, const QString& password) {
|
void SpotifyService::Login(const QString& username, const QString& password) {
|
||||||
qLog(Debug) << Q_FUNC_INFO;
|
|
||||||
delete server_;
|
delete server_;
|
||||||
delete blob_process_;
|
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);
|
server_ = new SpotifyServer(this);
|
||||||
blob_process_ = new QProcess(this);
|
blob_process_ = new QProcess(this);
|
||||||
blob_process_->setProcessChannelMode(QProcess::ForwardedChannels);
|
blob_process_->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||||
|
|
||||||
connect(server_, SIGNAL(ClientConnected()), SLOT(ClientConnected()));
|
|
||||||
connect(server_, SIGNAL(LoginCompleted(bool)), SLOT(LoginCompleted(bool)));
|
connect(server_, SIGNAL(LoginCompleted(bool)), SLOT(LoginCompleted(bool)));
|
||||||
|
connect(server_, SIGNAL(PlaylistsUpdated(protobuf::Playlists)),
|
||||||
|
SLOT(PlaylistsUpdated(protobuf::Playlists)));
|
||||||
|
|
||||||
connect(blob_process_,
|
connect(blob_process_,
|
||||||
SIGNAL(error(QProcess::ProcessError)),
|
SIGNAL(error(QProcess::ProcessError)),
|
||||||
SLOT(BlobProcessError(QProcess::ProcessError)));
|
SLOT(BlobProcessError(QProcess::ProcessError)));
|
||||||
|
|
||||||
pending_username_ = username;
|
|
||||||
pending_password_ = password;
|
|
||||||
|
|
||||||
server_->Init();
|
server_->Init();
|
||||||
blob_process_->start(
|
blob_process_->start(
|
||||||
blob_path_, QStringList() << QString::number(server_->server_port()));
|
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() {
|
void SpotifyService::PlaylistsUpdated(const protobuf::Playlists& response) {
|
||||||
qLog(Debug) << "ClientConnected";
|
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) {
|
inbox_ = new QStandardItem(IconLoader::Load("mail-message"), tr("Inbox"));
|
||||||
emit LoginFinished(success);
|
inbox_->setData(Type_InboxPlaylist, RadioModel::Role_Type);
|
||||||
}
|
inbox_->setData(true, RadioModel::Role_CanLazyLoad);
|
||||||
|
|
||||||
void SpotifyService::BlobProcessError(QProcess::ProcessError error) {
|
root_->appendRow(starred_);
|
||||||
qLog(Error) << "Failed to start blob process:" << error;
|
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
|
#ifndef SPOTIFYSERVICE_H
|
||||||
#define SPOTIFYSERVICE_H
|
#define SPOTIFYSERVICE_H
|
||||||
|
|
||||||
|
#include "radiomodel.h"
|
||||||
#include "radioservice.h"
|
#include "radioservice.h"
|
||||||
|
#include "spotifyblob/spotifymessages.pb.h"
|
||||||
|
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@ -12,14 +14,23 @@ class SpotifyService : public RadioService {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SpotifyService(RadioModel* parent);
|
SpotifyService(RadioModel* parent);
|
||||||
virtual ~SpotifyService();
|
~SpotifyService();
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
Type_StarredPlaylist = RadioModel::TypeCount,
|
||||||
|
Type_InboxPlaylist,
|
||||||
|
Type_Playlist,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
Role_PlaylistIndex = RadioModel::RoleCount,
|
||||||
|
};
|
||||||
|
|
||||||
virtual QStandardItem* CreateRootItem();
|
virtual QStandardItem* CreateRootItem();
|
||||||
virtual void LazyPopulate(QStandardItem* parent);
|
virtual void LazyPopulate(QStandardItem* parent);
|
||||||
|
|
||||||
void Login(const QString& username, const QString& password);
|
void Login(const QString& username, const QString& password);
|
||||||
void Search(const QString& query);
|
|
||||||
|
|
||||||
static const char* kServiceName;
|
static const char* kServiceName;
|
||||||
static const char* kSettingsGroup;
|
static const char* kSettingsGroup;
|
||||||
@ -30,10 +41,14 @@ signals:
|
|||||||
protected:
|
protected:
|
||||||
virtual QModelIndex GetCurrentIndex();
|
virtual QModelIndex GetCurrentIndex();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void EnsureServerCreated(const QString& username = QString(),
|
||||||
|
const QString& password = QString());
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ClientConnected();
|
|
||||||
void LoginCompleted(bool success);
|
|
||||||
void BlobProcessError(QProcess::ProcessError error);
|
void BlobProcessError(QProcess::ProcessError error);
|
||||||
|
void LoginCompleted(bool success);
|
||||||
|
void PlaylistsUpdated(const protobuf::Playlists& response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SpotifyServer* server_;
|
SpotifyServer* server_;
|
||||||
@ -41,8 +56,12 @@ private:
|
|||||||
QString blob_path_;
|
QString blob_path_;
|
||||||
QProcess* blob_process_;
|
QProcess* blob_process_;
|
||||||
|
|
||||||
QString pending_username_;
|
QStandardItem* root_;
|
||||||
QString pending_password_;
|
QStandardItem* starred_;
|
||||||
|
QStandardItem* inbox_;
|
||||||
|
QList<QStandardItem*> playlists_;
|
||||||
|
|
||||||
|
int login_task_id_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user