Start of some spotify support - builds a binary blob linked against libspotify that Clementine starts as a separate process.

This commit is contained in:
David Sansome 2011-04-25 19:16:26 +00:00
parent 4d19917b05
commit e152e3a3e3
28 changed files with 1349 additions and 27 deletions

View File

@ -46,6 +46,7 @@ find_package(OpenGL REQUIRED)
find_package(Boost REQUIRED)
find_package(Gettext REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(Protobuf)
pkg_check_modules(TAGLIB REQUIRED taglib>=1.6)
pkg_check_modules(GSTREAMER gstreamer-0.10)
@ -61,6 +62,7 @@ pkg_check_modules(USBMUXD libusbmuxd)
pkg_check_modules(LIBMTP libmtp>=1.0)
pkg_check_modules(INDICATEQT indicate-qt)
pkg_check_modules(ARCHIVE libarchive)
pkg_check_modules(SPOTIFY libspotify)
if (WIN32)
find_package(ZLIB REQUIRED)
@ -103,11 +105,13 @@ find_path(LASTFM_INCLUDE_DIRS lastfm/ws.h)
if (APPLE)
find_library(GROWL Growl)
option(ENABLE_SPARKLE "Sparkle updating" ON)
find_library(SPARKLE Sparkle)
if (ENABLE_SPARKLE AND SPARKLE)
set(HAVE_SPARKLE ON)
endif (ENABLE_SPARKLE AND SPARKLE)
# Uses Darwin kernel version.
# 9.8.0 -> 10.5/Leopard
# 10.4.0 -> 10.6/Snow Leopard
@ -177,6 +181,7 @@ option(ENABLE_SCRIPTING_ARCHIVES "Enable support for loading scripts from archiv
option(ENABLE_SCRIPTING_PYTHON "Enable python scripting" ON)
option(ENABLE_REMOTE "Enable support for using remote controls with Clementine" OFF)
option(ENABLE_BREAKPAD "Enable crash reporting" OFF)
option(ENABLE_SPOTIFY "Enable spotify support" OFF)
if(WIN32)
option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" OFF)
@ -222,6 +227,11 @@ if(ENABLE_BREAKPAD)
set(HAVE_BREAKPAD ON)
endif(ENABLE_BREAKPAD)
if(ENABLE_SPOTIFY AND SPOTIFY_FOUND AND PROTOBUF_FOUND)
set(HAVE_SPOTIFY ON)
endif(ENABLE_SPOTIFY AND SPOTIFY_FOUND AND PROTOBUF_FOUND)
if(ENABLE_VISUALISATIONS)
# When/if upstream accepts our patches then these options can be used to link
# to system installed projectM instead.
@ -357,6 +367,10 @@ if(HAVE_BREAKPAD)
add_subdirectory(3rdparty/google-breakpad)
endif(HAVE_BREAKPAD)
if(HAVE_SPOTIFY)
add_subdirectory(spotifyblob)
endif(HAVE_SPOTIFY)
# Uninstall support
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
@ -382,4 +396,5 @@ summary_add("Scripting support: loading archives" HAVE_LIBARCHIVE)
summary_add("Scripting support: Python" HAVE_SCRIPTING_PYTHON)
summary_add("(Mac OS X) Sparkle integration" HAVE_SPARKLE)
summary_add("(unstable) Remote control support" HAVE_REMOTE)
summary_add("(unstable) Spotify support" HAVE_SPOTIFY)
summary_show()

View File

@ -49,6 +49,7 @@
<file>providers/magnatune.png</file>
<file>schema/schema-8.sql</file>
<file>schema/schema-9.sql</file>
<file>icons/svg/spotify.svg</file>
<file>icons/22x22/application-exit.png</file>
<file>icons/22x22/applications-internet.png</file>
<file>icons/22x22/configure.png</file>

285
data/icons/svg/spotify.svg Normal file
View File

@ -0,0 +1,285 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
version="1.0"
sodipodi:docname="spotify.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient7729">
<stop
style="stop-color:#467700;stop-opacity:1"
offset="0"
id="stop7731" />
<stop
style="stop-color:#518900;stop-opacity:1"
offset="1"
id="stop7733" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient7721">
<stop
style="stop-color:#1a4300;stop-opacity:1"
offset="0"
id="stop7723" />
<stop
style="stop-color:#215300;stop-opacity:1"
offset="1"
id="stop7725" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient7713">
<stop
style="stop-color:#193d00;stop-opacity:1"
offset="0"
id="stop7715" />
<stop
style="stop-color:#1f4b00;stop-opacity:1"
offset="1"
id="stop7717" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient6355">
<stop
style="stop-color:#23371c;stop-opacity:1"
offset="0"
id="stop6357" />
<stop
style="stop-color:#004100;stop-opacity:1"
offset="1"
id="stop6359" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient6347">
<stop
style="stop-color:#abd033;stop-opacity:1"
offset="0"
id="stop6349" />
<stop
style="stop-color:#9ec02d;stop-opacity:0;"
offset="1"
id="stop6351" />
</linearGradient>
<linearGradient
id="linearGradient5519">
<stop
style="stop-color:#88ae04;stop-opacity:1"
offset="0"
id="stop5521" />
<stop
style="stop-color:#73a400;stop-opacity:1"
offset="1"
id="stop5523" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
<inkscape:perspective
id="perspective5505"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 526.18109 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5519"
id="linearGradient6853"
gradientUnits="userSpaceOnUse"
x1="119.5"
y1="91.362183"
x2="102.5"
y2="128.36218" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient6355"
id="radialGradient6855"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1318093,0.197699,-0.1703116,0.9750189,-125.44033,-100.50883)"
cx="147.76869"
cy="120.16964"
fx="147.76869"
fy="120.16964"
r="14.451495" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6347"
id="linearGradient6857"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-125.45979,-77.491488)"
x1="149.09062"
y1="85.104187"
x2="139.1649"
y2="106.61218" />
<filter
inkscape:collect="always"
id="filter7655"
x="-1.0712544"
width="3.1425087"
y="-1.0712544"
height="3.1425087">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="20.086019"
id="feGaussianBlur7657" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7713"
id="linearGradient7719"
x1="38.714096"
y1="17.417631"
x2="31.1127"
y2="15.165503"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.999924,1.232938e-2,-1.232938e-2,0.999924,0.2587749,-0.2864042)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7721"
id="linearGradient7727"
x1="35.708893"
y1="25.90625"
x2="27.400389"
y2="22.312458"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.965063,1.1899533e-2,-1.232938e-2,0.999924,1.1112913,-0.2758924)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7729"
id="linearGradient7735"
x1="35.607697"
y1="31.414557"
x2="23.13369"
y2="26.433243"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8596429,1.059967e-2,-1.232938e-2,0.999924,3.5039934,-0.2463897)" />
<filter
inkscape:collect="always"
id="filter7785"
x="-0.089479765"
width="1.1789595"
y="-0.14834692"
height="1.2966938">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.5684595"
id="feGaussianBlur7787" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6568542"
inkscape:cx="-0.10040691"
inkscape:cy="47.355194"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:snap-nodes="false"
inkscape:window-width="1440"
inkscape:window-height="853"
inkscape:window-x="0"
inkscape:window-y="25">
<inkscape:grid
type="xygrid"
id="grid5511"
visible="true"
enabled="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g6847"
transform="translate(-0.29021,-0.7456946)">
<path
sodipodi:type="arc"
style="opacity:0.64566926;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter7655);enable-background:accumulate"
id="path6363"
sodipodi:cx="112.5"
sodipodi:cy="109.86218"
sodipodi:rx="22.5"
sodipodi:ry="22.5"
d="M 135,109.86218 A 22.5,22.5 0 1 1 90,109.86218 A 22.5,22.5 0 1 1 135,109.86218 z"
transform="matrix(0.4666666,0,0,0.111111,-28.709782,33.038802)" />
<path
transform="translate(-88.70979,-85.616488)"
d="M 135,109.86218 A 22.5,22.5 0 1 1 90,109.86218 A 22.5,22.5 0 1 1 135,109.86218 z"
sodipodi:ry="22.5"
sodipodi:rx="22.5"
sodipodi:cy="109.86218"
sodipodi:cx="112.5"
id="path5513"
style="opacity:1;fill:url(#linearGradient6853);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="csccscc"
id="path5529"
d="M 7.6378996,39.420202 C 8.1682296,39.420202 11.29843,32.092852 23.23086,33.507062 C 35.16329,34.921272 36.54089,42.778952 36.54089,42.778952 C 34.49039,44.179572 32.3092,45.405922 29.55821,45.872552 C 29.55821,45.872552 26.55929,40.325532 21.1676,40.679082 C 15.77591,41.032632 13.91347,44.193172 13.91347,44.193172 C 11.14188,43.198092 8.9957096,41.593432 7.6378996,39.420202 z"
style="fill:url(#radialGradient6855);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="cccc"
id="path5531"
d="M 2.6652096,29.120692 C 35.315841,10.631207 30.66521,27.370692 44.66521,26.495692 C 44.14296,15.562843 35.20292,3.7456934 23.66521,3.7456934 C 6.6997296,4.3119254 1.9479196,20.58416 2.6652096,29.120692 z"
style="fill:url(#linearGradient6857);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter7785)" />
</g>
<path
style="opacity:1;fill:url(#linearGradient7719);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 10.300245,11.619555 C 8.241027,12.012805 7.9425774,15.355405 10.765465,15.313073 C 21.977063,13.20149 31.048724,15.49889 39.670046,19.826041 C 40.808245,20.378971 42.647611,18.028703 41.02047,16.758511 C 31.316836,11.614062 19.960975,9.8780472 10.300245,11.619555 z"
id="path7661"
sodipodi:nodetypes="ccccc" />
<path
style="opacity:1;fill:url(#linearGradient7727);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 12.303532,19.144827 C 10.172718,19.381037 9.829391,22.969311 12.56196,22.64828 C 21.903655,21.50982 27.85363,22.62879 35.683323,25.808592 C 37.647192,26.873264 38.724998,23.482585 36.929338,22.604961 C 28.133565,18.889799 21.412131,18.726151 12.303532,19.144827 z"
id="path7666"
sodipodi:nodetypes="ccccc" />
<path
style="opacity:1;fill:url(#linearGradient7735);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
d="M 14.447653,25.703011 C 12.75416,26.30833 13.053805,29.148649 14.863112,29.052139 C 17.870832,28.722372 20.153948,28.706138 23.615532,29.097554 C 27.126432,29.494545 29.233258,30.435974 32.157356,31.453048 C 33.350406,31.961876 34.447173,29.764482 33.187538,28.528027 C 29.966426,27.274723 27.415802,26.174879 23.388506,25.719498 C 20.084015,25.348048 17.755584,25.371512 14.447653,25.703011 z"
id="path7668"
sodipodi:nodetypes="ccscccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,51 @@
include_directories(${SPOTIFY_INCLUDE_DIRS})
include_directories(${PROTOBUF_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_SOURCE_DIR}/src)
link_directories(${SPOTIFY_LIBRARY_DIRS})
set(EXECUTABLE_OUTPUT_PATH ..)
set(SOURCES
main.cpp
spotifyblob.cpp
spotifyclient.cpp
../src/core/logging.cpp
)
set(HEADERS
spotifyblob.h
spotifyclient.h
)
set(MESSAGES
messages.proto
)
qt4_wrap_cpp(MOC ${HEADERS})
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
add_library(clementine-spotifyblob-messages STATIC
${PROTO_SOURCES}
)
target_link_libraries(clementine-spotifyblob-messages
${PROTOBUF_LIBRARY}
${CMAKE_THREAD_LIBS_INIT}
)
add_executable(clementine-spotifyblob
${SOURCES}
${MOC}
)
target_link_libraries(clementine-spotifyblob
${SPOTIFY_LIBRARIES}
${QT_LIBRARIES}
clementine-spotifyblob-messages
)

25
spotifyblob/main.cpp Normal file
View File

@ -0,0 +1,25 @@
#include <QCoreApplication>
#include <QStringList>
#include "spotifyblob.h"
#include "spotifyclient.h"
#include "core/logging.h"
int main(int argc, char** argv) {
QCoreApplication a(argc, argv);
logging::Init();
const QStringList arguments(a.arguments());
if (arguments.length() != 2) {
qFatal("Usage: %s port", argv[0]);
}
SpotifyClient client;
SpotifyBlob blob(&client);
client.Init(arguments[1].toInt());
return a.exec();
}

View File

@ -0,0 +1,17 @@
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;
}

0
spotifyblob/spotify.cpp Normal file
View File

0
spotifyblob/spotify.h Normal file
View File

124
spotifyblob/spotifyblob.cpp Normal file
View File

@ -0,0 +1,124 @@
#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);
}

44
spotifyblob/spotifyblob.h Normal file
View File

@ -0,0 +1,44 @@
#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

@ -0,0 +1,90 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "messages.pb.h"
#include "spotifyclient.h"
#include "core/logging.h"
#include <QHostAddress>
#include <QTcpSocket>
#include <boost/scoped_array.hpp>
SpotifyClient::SpotifyClient(QObject* parent)
: QObject(parent),
socket_(new QTcpSocket(this))
{
connect(socket_, SIGNAL(readyRead()), SLOT(SocketReadyRead()));
}
void SpotifyClient::Init(quint16 port) {
qLog(Debug) << "port" << port;
socket_->connectToHost(QHostAddress::LocalHost, port);
}
void SpotifyClient::SocketReadyRead() {
QDataStream s(socket_);
quint32 length = 0;
s >> length;
if (length == 0) {
return;
}
boost::scoped_array<char> data(new char[length]);
s.readRawData(data.get(), length);
RequestMessage message;
if (!message.ParseFromArray(data.get(), length)) {
qLog(Error) << "Malformed protobuf 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()));
}
}
void SpotifyClient::SendMessage(const ResponseMessage& message) {
qLog(Debug) << message.DebugString().c_str();
std::string data(message.SerializeAsString());
QDataStream s(socket_);
s << quint32(data.length());
s.writeRawData(data.data(), data.length());
}
void SpotifyClient::LoginCompleted(bool success, const QString& error) {
const QByteArray error_bytes(error.toUtf8());
ResponseMessage message;
LoginResponse* response = message.mutable_login_response();
response->set_success(success);
response->set_error(error_bytes.constData(), error_bytes.length());
SendMessage(message);
}

View File

@ -0,0 +1,32 @@
#ifndef SPOTIFYCLIENT_H
#define SPOTIFYCLIENT_H
#include <QObject>
class QTcpSocket;
class ResponseMessage;
class SpotifyClient : public QObject {
Q_OBJECT
public:
SpotifyClient(QObject* parent = 0);
void Init(quint16 port);
void LoginCompleted(bool success, const QString& error);
signals:
void Login(const QString& username, const QString& password);
private slots:
void SocketReadyRead();
private:
void SendMessage(const ResponseMessage& message);
QTcpSocket* socket_;
};
#endif // SPOTIFYCLIENT_H

28
spotifyblob/spotifykey.h Normal file
View File

@ -0,0 +1,28 @@
#include <stdint.h>
#include <stdlib.h>
const uint8_t g_appkey[] = {
0x01, 0x59, 0x4E, 0xAE, 0xF2, 0x64, 0x2B, 0x1F, 0x13, 0xF8, 0xB1, 0x2C, 0x0A, 0x4F, 0x8A, 0xCA,
0x5D, 0xB8, 0x23, 0x43, 0x12, 0xB2, 0x3A, 0x21, 0x64, 0x0B, 0x4C, 0x17, 0x39, 0xB6, 0x3B, 0x92,
0xE6, 0xB3, 0x56, 0xE6, 0x01, 0x61, 0x56, 0x81, 0xD6, 0x5A, 0x1E, 0x4A, 0x72, 0xA7, 0x34, 0x89,
0x3E, 0x64, 0x9D, 0xF9, 0x68, 0xB9, 0xD3, 0x0C, 0x20, 0x27, 0x05, 0x42, 0x53, 0x4B, 0x2A, 0xE0,
0xB7, 0xA9, 0xD7, 0x30, 0x78, 0xB7, 0xA6, 0x7C, 0x87, 0x6E, 0x8D, 0x2B, 0xAF, 0xB3, 0xF2, 0x09,
0xA4, 0x1D, 0x59, 0x15, 0xF4, 0x34, 0x4F, 0x91, 0x18, 0x1E, 0x6D, 0xC5, 0x24, 0x59, 0x30, 0xD3,
0x7E, 0x5C, 0x15, 0x3C, 0xA5, 0x85, 0xE3, 0xE8, 0x1D, 0x46, 0x35, 0x30, 0xA6, 0xBB, 0x2A, 0x07,
0x0E, 0x8E, 0x8A, 0x87, 0xD1, 0x2C, 0x9C, 0xDE, 0x29, 0xAC, 0x5B, 0x99, 0xA5, 0x06, 0xAA, 0x44,
0xA4, 0xC5, 0x52, 0xCE, 0x89, 0x70, 0xBD, 0x97, 0x1C, 0xA5, 0x36, 0xAE, 0xA3, 0xBB, 0xB4, 0x3B,
0xB9, 0x8F, 0x2E, 0xF1, 0x79, 0x03, 0x52, 0xC5, 0x4C, 0xDD, 0x82, 0x31, 0x89, 0x6C, 0xB6, 0xF8,
0x43, 0x3B, 0x23, 0xF9, 0x61, 0x6A, 0xDC, 0xD3, 0xF7, 0x1A, 0xEA, 0xAF, 0x11, 0xDE, 0xDA, 0x58,
0x3B, 0x0B, 0x33, 0xB0, 0x17, 0x99, 0x2E, 0x15, 0xDC, 0x20, 0x9A, 0x2F, 0x0C, 0x43, 0xBD, 0xB1,
0x4D, 0x85, 0xC8, 0x2A, 0x73, 0x65, 0x3A, 0xBC, 0xBF, 0x84, 0x90, 0xE5, 0x37, 0xD9, 0x4A, 0xC6,
0x46, 0xD4, 0x04, 0xB8, 0x7E, 0xA7, 0x36, 0xD8, 0xBC, 0xEF, 0x76, 0x4B, 0x67, 0xD8, 0x28, 0xAE,
0x4F, 0x00, 0x7B, 0xD7, 0xE7, 0xE4, 0xA6, 0x48, 0x11, 0x9B, 0x87, 0xA7, 0x65, 0xA4, 0x77, 0x82,
0x1A, 0xA6, 0xFC, 0x30, 0xF7, 0x5A, 0x79, 0x9E, 0x48, 0xD5, 0x8C, 0x4C, 0x70, 0x39, 0x31, 0x6B,
0x6F, 0xAD, 0x6C, 0xB0, 0x3F, 0x32, 0xE6, 0xAD, 0xEE, 0x02, 0x80, 0xF6, 0xEE, 0x7B, 0x4C, 0x50,
0xAD, 0x62, 0x3B, 0xA7, 0xFE, 0xEF, 0xE2, 0xFC, 0xE3, 0x70, 0x74, 0x12, 0x10, 0xAD, 0xE3, 0xF3,
0x5D, 0x98, 0xE2, 0xA4, 0xED, 0xF6, 0x91, 0x89, 0x90, 0x02, 0x20, 0xA4, 0x37, 0x5A, 0x7C, 0xB2,
0x04, 0x04, 0x70, 0x5E, 0x37, 0x43, 0x16, 0x95, 0xC1, 0x71, 0xA8, 0xD9, 0x7D, 0x2B, 0x46, 0x72,
0x5F,
};
const size_t g_appkey_size = sizeof(g_appkey);

View File

@ -613,6 +613,21 @@ if(HAVE_LIBLASTFM)
)
endif(HAVE_LIBLASTFM)
if(HAVE_SPOTIFY)
list(APPEND SOURCES
radio/spotifyconfig.cpp
radio/spotifyserver.cpp
radio/spotifyservice.cpp
)
list(APPEND HEADERS
radio/spotifyconfig.h
radio/spotifyserver.h
radio/spotifyservice.h
)
list(APPEND UI
radio/spotifyconfig.ui)
endif(HAVE_SPOTIFY)
if(APPLE)
list(APPEND HEADERS core/macglobalshortcutbackend.h)
list(APPEND HEADERS devices/macdevicelister.h)
@ -995,6 +1010,10 @@ if(HAVE_BREAKPAD)
endif (LINUX)
endif(HAVE_BREAKPAD)
if(HAVE_SPOTIFY)
target_link_libraries(clementine_lib clementine-spotifyblob-messages)
endif(HAVE_SPOTIFY)
if (APPLE)
target_link_libraries(clementine_lib
${GROWL}

View File

@ -33,6 +33,7 @@
#cmakedefine HAVE_REMOTE
#cmakedefine HAVE_SCRIPTING_PYTHON
#cmakedefine HAVE_SPARKLE
#cmakedefine HAVE_SPOTIFY
#cmakedefine HAVE_STATIC_SQLITE
#cmakedefine HAVE_WIIMOTEDEV
#cmakedefine LEOPARD

View File

@ -1,18 +1,17 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Copyright 2011, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <QCoreApplication>
@ -37,8 +36,7 @@ static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
static QtMsgHandler sOriginalMessageHandler = NULL;
static void GLog(const gchar* domain, GLogLevelFlags level,
const gchar* message, gpointer user_data) {
void GLog(const char* domain, int level, const char* message, void* user_data) {
switch (level) {
case G_LOG_FLAG_RECURSION:
case G_LOG_FLAG_FATAL:
@ -84,9 +82,6 @@ void Init() {
sClassLevels = new QMap<QString, Level>();
sNullDevice = new NullDevice;
// Glib integration
g_log_set_default_handler(&GLog, NULL);
// Catch other messages from Qt
if (!sOriginalMessageHandler) {
sOriginalMessageHandler = qInstallMsgHandler(MessageHandler);

View File

@ -1,18 +1,17 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Copyright 2011, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef LOGGING_H
@ -46,6 +45,8 @@ namespace logging {
QDebug CreateLogger(Level level, const char* pretty_function, int line);
void GLog(const char* domain, int level, const char* message, void* user_data);
extern const char* kDefaultLogLevels;
}

View File

@ -314,6 +314,7 @@ int main(int argc, char *argv[]) {
// Initialise logging
logging::Init();
logging::SetLevels(options.log_levels());
g_log_set_default_handler(reinterpret_cast<GLogFunc>(&logging::GLog), NULL);
QtSingleApplication a(argc, argv);
#ifdef Q_OS_DARWIN

View File

@ -29,6 +29,9 @@
#ifdef HAVE_LIBLASTFM
#include "lastfmservice.h"
#endif
#ifdef HAVE_SPOTIFY
#include "spotifyservice.h"
#endif
#include <QMimeData>
#include <QtDebug>
@ -51,6 +54,9 @@ RadioModel::RadioModel(BackgroundThread<Database>* db_thread,
#ifdef HAVE_LIBLASTFM
AddService(new LastFMService(this));
#endif
#ifdef HAVE_SPOTIFY
AddService(new SpotifyService(this));
#endif
AddService(new SomaFMService(this));
AddService(new MagnatuneService(this));
@ -70,6 +76,7 @@ void RadioModel::AddService(RadioService *service) {
root->setData(QVariant::fromValue(service), Role_Service);
invisibleRootItem()->appendRow(root);
qDebug() << "Adding:" << service->name();
sServices->insert(service->name(), service);
connect(service, SIGNAL(AsyncLoadFinished(PlaylistItem::SpecialLoadResult)), SIGNAL(AsyncLoadFinished(PlaylistItem::SpecialLoadResult)));

View File

@ -0,0 +1,80 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "spotifyconfig.h"
#include "core/network.h"
#include "spotifyservice.h"
#include "radiomodel.h"
#include "ui_spotifyconfig.h"
#include <QMessageBox>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSettings>
#include <QtDebug>
SpotifyConfig::SpotifyConfig(QWidget *parent)
: QWidget(parent),
network_(new NetworkAccessManager(this)),
ui_(new Ui_SpotifyConfig),
service_(RadioModel::Service<SpotifyService>()),
needs_validation_(true)
{
ui_->setupUi(this);
ui_->busy->hide();
connect(service_, SIGNAL(LoginFinished(bool)), SLOT(LoginFinished(bool)));
}
SpotifyConfig::~SpotifyConfig() {
delete ui_;
}
bool SpotifyConfig::NeedsValidation() const {
// FIXME
return needs_validation_;
}
void SpotifyConfig::Validate() {
ui_->busy->show();
service_->Login(ui_->username->text(), ui_->password->text());
}
void SpotifyConfig::Load() {
QSettings s;
s.beginGroup(SpotifyService::kSettingsGroup);
ui_->username->setText(s.value("username").toString());
ui_->password->setText(s.value("password").toString());
}
void SpotifyConfig::Save() {
QSettings s;
s.beginGroup(SpotifyService::kSettingsGroup);
s.setValue("username", ui_->username->text());
s.setValue("password", ui_->password->text());
RadioModel::Service<SpotifyService>()->ReloadSettings();
}
void SpotifyConfig::LoginFinished(bool success) {
qDebug() << Q_FUNC_INFO << success;
needs_validation_ = !success;
ui_->busy->hide();
emit ValidationComplete(success);
}

57
src/radio/spotifyconfig.h Normal file
View File

@ -0,0 +1,57 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPOTIFYCONFIG_H
#define SPOTIFYCONFIG_H
#include <QWidget>
class QAuthenticator;
class QNetworkReply;
class NetworkAccessManager;
class Ui_SpotifyConfig;
class SpotifyService;
class SpotifyConfig : public QWidget {
Q_OBJECT
public:
SpotifyConfig(QWidget* parent = 0);
~SpotifyConfig();
bool NeedsValidation() const;
void Validate();
signals:
void ValidationComplete(bool success);
public slots:
void Load();
void Save();
private slots:
void LoginFinished(bool success);
private:
NetworkAccessManager* network_;
Ui_SpotifyConfig* ui_;
SpotifyService* service_;
bool needs_validation_;
};
#endif // SPOTIFYCONFIG_H

125
src/radio/spotifyconfig.ui Normal file
View File

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SpotifyConfig</class>
<widget class="QWidget" name="SpotifyConfig">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>448</width>
<height>310</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Account details</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>A Spotify Premium account is required.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QWidget" name="login_container" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="margin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="username_label">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="username"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="password_label">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QWidget" name="busy" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Authenticating...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<extends>QLabel</extends>
<header>widgets/busyindicator.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

108
src/radio/spotifyserver.cpp Normal file
View File

@ -0,0 +1,108 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "spotifyserver.h"
#include "core/logging.h"
#include "spotifyblob/messages.pb.h"
#include <QTcpServer>
#include <QTcpSocket>
#include <boost/scoped_array.hpp>
SpotifyServer::SpotifyServer(QObject* parent)
: QObject(parent),
server_(new QTcpServer(this)),
protocol_socket_(NULL)
{
connect(server_, SIGNAL(newConnection()), SLOT(NewConnection()));
}
void SpotifyServer::Init() {
if (!server_->listen(QHostAddress::LocalHost)) {
qLog(Error) << "Couldn't open server socket" << server_->errorString();
}
}
int SpotifyServer::server_port() const {
return server_->serverPort();
}
void SpotifyServer::NewConnection() {
protocol_socket_ = server_->nextPendingConnection();
connect(protocol_socket_, SIGNAL(readyRead()), SLOT(ProtocolSocketReadyRead()));
qLog(Info) << "Connection from port" << protocol_socket_->peerPort();
emit ClientConnected();
}
void SpotifyServer::Login(const QString& username, const QString& password) {
const QByteArray username_bytes(username.toUtf8());
const QByteArray password_bytes(password.toUtf8());
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());
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";
protocol_socket_->deleteLater();
protocol_socket_ = NULL;
return;
}
qLog(Debug) << message.DebugString().c_str();
if (message.has_login_response()) {
const LoginResponse& response = message.login_response();
if (!response.success()) {
qLog(Info) << QString::fromUtf8(response.error().data(), response.error().size());
}
emit LoginCompleted(response.success());
}
}

54
src/radio/spotifyserver.h Normal file
View File

@ -0,0 +1,54 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPOTIFYSERVER_H
#define SPOTIFYSERVER_H
#include <QObject>
class RequestMessage;
class QTcpServer;
class QTcpSocket;
class SpotifyServer : public QObject {
Q_OBJECT
public:
SpotifyServer(QObject* parent = 0);
void Init();
void Login(const QString& username, const QString& password);
int server_port() const;
signals:
void ClientConnected();
void LoginCompleted(bool success);
private slots:
void NewConnection();
void ProtocolSocketReadyRead();
private:
void SendMessage(const RequestMessage& message);
QTcpServer* server_;
QTcpSocket* protocol_socket_;
};
#endif // SPOTIFYSERVER_H

View File

@ -0,0 +1,72 @@
#include "core/logging.h"
#include "radiomodel.h"
#include "spotifyserver.h"
#include "spotifyservice.h"
#include <QCoreApplication>
#include <QProcess>
const char* SpotifyService::kServiceName = "Spotify";
const char* SpotifyService::kSettingsGroup = "Spotify";
SpotifyService::SpotifyService(RadioModel* parent)
: RadioService(kServiceName, parent),
server_(NULL),
blob_process_(NULL) {
blob_path_ = QCoreApplication::applicationFilePath() + "-spotifyblob";
}
SpotifyService::~SpotifyService() {
}
QStandardItem* SpotifyService::CreateRootItem() {
QStandardItem* item = new QStandardItem(QIcon(":icons/svg/spotify.svg"), kServiceName);
item->setData(true, RadioModel::Role_CanLazyLoad);
return item;
}
void SpotifyService::LazyPopulate(QStandardItem* parent) {
return;
}
QModelIndex SpotifyService::GetCurrentIndex() {
return QModelIndex();
}
void SpotifyService::Login(const QString& username, const QString& password) {
qLog(Debug) << Q_FUNC_INFO;
delete server_;
delete blob_process_;
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(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()));
}
void SpotifyService::ClientConnected() {
qLog(Debug) << "ClientConnected";
server_->Login(pending_username_, pending_password_);
}
void SpotifyService::LoginCompleted(bool success) {
emit LoginFinished(success);
}
void SpotifyService::BlobProcessError(QProcess::ProcessError error) {
qLog(Error) << "Failed to start blob process:" << error;
}

View File

@ -0,0 +1,48 @@
#ifndef SPOTIFYSERVICE_H
#define SPOTIFYSERVICE_H
#include "radioservice.h"
#include <QProcess>
#include <QTimer>
class SpotifyServer;
class SpotifyService : public RadioService {
Q_OBJECT
public:
explicit SpotifyService(RadioModel* parent);
virtual ~SpotifyService();
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;
signals:
void LoginFinished(bool success);
protected:
virtual QModelIndex GetCurrentIndex();
private slots:
void ClientConnected();
void LoginCompleted(bool success);
void BlobProcessError(QProcess::ProcessError error);
private:
SpotifyServer* server_;
QString blob_path_;
QProcess* blob_process_;
QString pending_username_;
QString pending_password_;
};
#endif

View File

@ -45,6 +45,10 @@
# include "remote/remoteconfig.h"
#endif
#ifdef HAVE_SPOTIFY
# include "radio/spotifyconfig.h"
#endif
#include <QColorDialog>
#include <QDir>
#include <QFontDialog>
@ -118,6 +122,21 @@ SettingsDialog::SettingsDialog(BackgroundStreams* streams, QWidget* parent)
connect(lastfm_config_, SIGNAL(ValidationComplete(bool)), SLOT(ValidationComplete(bool)));
#endif
#ifdef HAVE_SPOTIFY
ui_->list->insertItem(Page_Spotify, tr("Spotify"));
ui_->list->item(Page_Spotify)->setIcon(QIcon(":/icons/svg/spotify.svg"));
QWidget* spotify_page = new QWidget;
QVBoxLayout* spotify_layout = new QVBoxLayout;
spotify_layout->setContentsMargins(0, 0, 0, 0);
spotify_config_ = new SpotifyConfig;
spotify_layout->addWidget(spotify_config_);
spotify_page->setLayout(spotify_layout);
ui_->stacked_widget->insertWidget(Page_Spotify, spotify_page);
connect(spotify_config_, SIGNAL(ValidationComplete(bool)), SLOT(ValidationComplete(bool)));
#endif
#ifdef HAVE_REMOTE
ui_->list->insertItem(Page_Remote, tr("Remote Control"));
ui_->list->item(Page_Remote)->setIcon(IconLoader::Load("network-server"));
@ -312,6 +331,16 @@ void SettingsDialog::accept() {
}
#endif
#ifdef HAVE_SPOTIFY
if (spotify_config_->NeedsValidation()) {
spotify_config_->Validate();
ui_->buttonBox->setEnabled(false);
return;
} else {
spotify_config_->Save();
}
#endif
if (ui_->magnatune->NeedsValidation()) {
ui_->magnatune->Validate();
ui_->buttonBox->setEnabled(false);
@ -524,6 +553,10 @@ void SettingsDialog::showEvent(QShowEvent*) {
remote_config_->Load();
#endif
#ifdef HAVE_SPOTIFY
spotify_config_->Load();
#endif
// Magnatune
ui_->magnatune->Load();

View File

@ -44,6 +44,9 @@ class Ui_SettingsDialog;
#ifdef HAVE_REMOTE
class RemoteConfig;
#endif
#ifdef HAVE_SPOTIFY
class SpotifyConfig;
#endif
class GstEngine;
@ -63,6 +66,9 @@ class SettingsDialog : public QDialog {
Page_Library,
#ifdef HAVE_LIBLASTFM
Page_Lastfm,
#endif
#ifdef HAVE_SPOTIFY
Page_Spotify,
#endif
Page_Magnatune,
Page_BackgroundStreams,
@ -126,6 +132,9 @@ class SettingsDialog : public QDialog {
#endif
#ifdef HAVE_REMOTE
RemoteConfig* remote_config_;
#endif
#ifdef HAVE_SPOTIFY
SpotifyConfig* spotify_config_;
#endif
const GstEngine* gst_engine_;