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:
David Sansome 2011-04-26 13:42:58 +00:00
parent e152e3a3e3
commit c4f1b3f002
22 changed files with 495 additions and 319 deletions

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

View File

@ -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}

View File

@ -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();

View File

@ -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;
}

View File

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View 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;
}

View 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());
}

View 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

View File

@ -72,6 +72,8 @@ public:
// a pointer to a RadioService. Services should not set this field
// themselves.
Role_Service,
RoleCount
};
enum Type {

View File

@ -29,6 +29,7 @@ class SpotifyService;
class SpotifyConfig : public QWidget {
Q_OBJECT
public:
SpotifyConfig(QWidget* parent = 0);
~SpotifyConfig();

View File

@ -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());
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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