From 3deb371537abc4c32c6f0721089fba60e6c645e2 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Fri, 29 Apr 2011 19:44:51 +0000 Subject: [PATCH] If the spotify blob isn't installed, try to download it from the Clementine website. Also, make the blob separate from the core Spotify code in Clementine so you can build the core code without building the blob --- CMakeLists.txt | 51 +++++-- cmake/SpotifyVersion.cmake | 2 + spotifyblob/blob/CMakeLists.txt | 54 +++++++ spotifyblob/{ => blob}/main.cpp | 0 spotifyblob/{ => blob}/spotifyclient.cpp | 0 spotifyblob/{ => blob}/spotifyclient.h | 0 spotifyblob/{ => blob}/spotifykey.h | 0 spotifyblob/{ => common}/CMakeLists.txt | 40 ++--- spotifyblob/common/blobversion.h.in | 23 +++ .../{ => common}/spotifymessagehandler.cpp | 3 - .../{ => common}/spotifymessagehandler.h | 0 .../{ => common}/spotifymessages.proto | 3 + src/CMakeLists.txt | 4 +- src/config.h.in | 1 + src/core/logging.cpp | 2 +- src/core/utilities.cpp | 3 + src/core/utilities.h | 1 + src/radio/spotifyblobdownloader.cpp | 137 ++++++++++++++++++ src/radio/spotifyblobdownloader.h | 59 ++++++++ src/radio/spotifyserver.cpp | 4 +- src/radio/spotifyserver.h | 2 +- src/radio/spotifyservice.cpp | 108 +++++++++++--- src/radio/spotifyservice.h | 9 +- 23 files changed, 432 insertions(+), 74 deletions(-) create mode 100644 cmake/SpotifyVersion.cmake create mode 100644 spotifyblob/blob/CMakeLists.txt rename spotifyblob/{ => blob}/main.cpp (100%) rename spotifyblob/{ => blob}/spotifyclient.cpp (100%) rename spotifyblob/{ => blob}/spotifyclient.h (100%) rename spotifyblob/{ => blob}/spotifykey.h (100%) rename spotifyblob/{ => common}/CMakeLists.txt (56%) create mode 100644 spotifyblob/common/blobversion.h.in rename spotifyblob/{ => common}/spotifymessagehandler.cpp (95%) rename spotifyblob/{ => common}/spotifymessagehandler.h (100%) rename spotifyblob/{ => common}/spotifymessages.proto (98%) create mode 100644 src/radio/spotifyblobdownloader.cpp create mode 100644 src/radio/spotifyblobdownloader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 941ce321c..69c873377 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ include(cmake/Version.cmake) include(cmake/Deb.cmake) include(cmake/Rpm.cmake) include(cmake/SipBindings.cmake) +include(cmake/SpotifyVersion.cmake) if (UNIX AND NOT APPLE) set(LINUX 1) @@ -137,6 +138,16 @@ if(${CMAKE_BUILD_TYPE} MATCHES "Release") add_definitions(-DQT_NO_DEBUG_OUTPUT) endif(${CMAKE_BUILD_TYPE} MATCHES "Release") +# Use protobuf-lite if it's available +find_library(PROTOBUF_LITE_LIBRARY protobuf-lite) +if(NOT PROTOBUF_LITE_LIBRARY) + # Lucid doesn't have a .so symlink + find_file(PROTOBUF_LITE_LIBRARY + NAMES libprotobuf-lite.so.5 + PATH_SUFFIXES lib + ) +endif(NOT PROTOBUF_LITE_LIBRARY) + # Set up definitions and paths add_definitions(${QT_DEFINITIONS}) link_directories(${TAGLIB_LIBRARY_DIRS}) @@ -188,7 +199,8 @@ 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) +option(ENABLE_SPOTIFY_BLOB "Build the spotify non-GPL binary" ON) +option(ENABLE_SPOTIFY "Enable spotify support" ON) if(WIN32) option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" OFF) @@ -234,9 +246,13 @@ if(ENABLE_BREAKPAD) set(HAVE_BREAKPAD ON) endif(ENABLE_BREAKPAD) -if(ENABLE_SPOTIFY AND SPOTIFY_FOUND AND PROTOBUF_FOUND) +if(ENABLE_SPOTIFY AND PROTOBUF_FOUND) set(HAVE_SPOTIFY ON) -endif(ENABLE_SPOTIFY AND SPOTIFY_FOUND AND PROTOBUF_FOUND) +endif(ENABLE_SPOTIFY AND PROTOBUF_FOUND) + +if(ENABLE_SPOTIFY_BLOB AND PROTOBUF_FOUND AND SPOTIFY_FOUND) + set(HAVE_SPOTIFY_BLOB ON) +endif(ENABLE_SPOTIFY_BLOB AND PROTOBUF_FOUND AND SPOTIFY_FOUND) if(ENABLE_VISUALISATIONS) @@ -375,9 +391,13 @@ if(HAVE_BREAKPAD) endif(HAVE_BREAKPAD) if(HAVE_SPOTIFY) - add_subdirectory(spotifyblob) + add_subdirectory(spotifyblob/common) endif(HAVE_SPOTIFY) +if(HAVE_SPOTIFY_BLOB) + add_subdirectory(spotifyblob/blob) +endif(HAVE_SPOTIFY_BLOB) + # Uninstall support configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" @@ -388,20 +408,21 @@ add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") # Show a summary of what we have enabled -summary_add("devices: DeviceKit backend" HAVE_DEVICEKIT) -summary_add("devices: iPod classic support" HAVE_LIBGPOD) -summary_add("devices: iPod Touch, iPhone, iPad support" HAVE_IMOBILEDEVICE) -summary_add("devices: MTP support" HAVE_LIBMTP) -summary_add("devices: GIO backend" HAVE_GIO) -summary_add("D-Bus support" HAVE_DBUS) -summary_add("Gnome sound menu integration" HAVE_LIBINDICATE) -summary_add("Wiimote support" HAVE_WIIMOTEDEV) -summary_add("Visualisations" ENABLE_VISUALISATIONS) -summary_add("Last.fm support" HAVE_LIBLASTFM) summary_add("Crash reporting" HAVE_BREAKPAD) +summary_add("D-Bus support" HAVE_DBUS) +summary_add("Devices: DeviceKit backend" HAVE_DEVICEKIT) +summary_add("Devices: iPod classic support" HAVE_LIBGPOD) +summary_add("Devices: iPod Touch, iPhone, iPad support" HAVE_IMOBILEDEVICE) +summary_add("Devices: MTP support" HAVE_LIBMTP) +summary_add("Devices: GIO backend" HAVE_GIO) +summary_add("Gnome sound menu integration" HAVE_LIBINDICATE) +summary_add("Last.fm support" HAVE_LIBLASTFM) summary_add("Scripting support: loading archives" HAVE_LIBARCHIVE) summary_add("Scripting support: Python" HAVE_SCRIPTING_PYTHON) +summary_add("Spotify support: core code" HAVE_SPOTIFY) +summary_add("Spotify support: non-GPL binary helper" HAVE_SPOTIFY_BLOB) +summary_add("Visualisations" ENABLE_VISUALISATIONS) +summary_add("Wiimote support" HAVE_WIIMOTEDEV) 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() diff --git a/cmake/SpotifyVersion.cmake b/cmake/SpotifyVersion.cmake new file mode 100644 index 000000000..7cf6bc0b3 --- /dev/null +++ b/cmake/SpotifyVersion.cmake @@ -0,0 +1,2 @@ +# Increment this whenever the user needs to download a new blob +set(SPOTIFY_BLOB_VERSION 1) diff --git a/spotifyblob/blob/CMakeLists.txt b/spotifyblob/blob/CMakeLists.txt new file mode 100644 index 000000000..4a69eb559 --- /dev/null +++ b/spotifyblob/blob/CMakeLists.txt @@ -0,0 +1,54 @@ +include_directories(${SPOTIFY_INCLUDE_DIRS}) +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/../common) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../common) +include_directories(${CMAKE_SOURCE_DIR}/src) + +link_directories(${SPOTIFY_LIBRARY_DIRS}) + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) + +set(SOURCES + main.cpp + spotifyclient.cpp + + ${CMAKE_SOURCE_DIR}/src/core/logging.cpp +) + +set(HEADERS + spotifyclient.h +) + +qt4_wrap_cpp(MOC ${HEADERS}) + +add_executable(clementine-spotifyblob + ${SOURCES} + ${MOC} +) + +target_link_libraries(clementine-spotifyblob + ${SPOTIFY_LIBRARIES} + ${QT_QTCORE_LIBRARY} + ${QT_QTNETWORK_LIBRARY} + clementine-spotifyblob-messages +) + +install(TARGETS clementine-spotifyblob + RUNTIME DESTINATION bin +) + +if(LINUX) + # Versioned name of the blob + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(SPOTIFY_BLOB_ARCH 32) + else(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(SPOTIFY_BLOB_ARCH 64) + endif(CMAKE_SIZEOF_VOID_P EQUAL 4) + + install( + FILES ${CMAKE_BINARY_DIR}/clementine-spotifyblob + DESTINATION ${CMAKE_BINARY_DIR}/spotify/version${SPOTIFY_BLOB_VERSION}-${SPOTIFY_BLOB_ARCH}bit/blob + RENAME ${SPOTIFY_BLOB_NAME} + ) +endif(LINUX) diff --git a/spotifyblob/main.cpp b/spotifyblob/blob/main.cpp similarity index 100% rename from spotifyblob/main.cpp rename to spotifyblob/blob/main.cpp diff --git a/spotifyblob/spotifyclient.cpp b/spotifyblob/blob/spotifyclient.cpp similarity index 100% rename from spotifyblob/spotifyclient.cpp rename to spotifyblob/blob/spotifyclient.cpp diff --git a/spotifyblob/spotifyclient.h b/spotifyblob/blob/spotifyclient.h similarity index 100% rename from spotifyblob/spotifyclient.h rename to spotifyblob/blob/spotifyclient.h diff --git a/spotifyblob/spotifykey.h b/spotifyblob/blob/spotifykey.h similarity index 100% rename from spotifyblob/spotifykey.h rename to spotifyblob/blob/spotifykey.h diff --git a/spotifyblob/CMakeLists.txt b/spotifyblob/common/CMakeLists.txt similarity index 56% rename from spotifyblob/CMakeLists.txt rename to spotifyblob/common/CMakeLists.txt index d0a924acf..0dddd8d1a 100644 --- a/spotifyblob/CMakeLists.txt +++ b/spotifyblob/common/CMakeLists.txt @@ -1,12 +1,9 @@ -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 ..) - +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/blobversion.h.in + ${CMAKE_CURRENT_BINARY_DIR}/blobversion.h) set(COMMON_SOURCES spotifymessagehandler.cpp @@ -29,32 +26,15 @@ add_library(clementine-spotifyblob-messages STATIC ${PROTO_SOURCES} ) +# Use protobuf-lite if it's available +if(PROTOBUF_LITE_LIBRARY) + set(protobuf ${PROTOBUF_LITE_LIBRARY}) +else(PROTOBUF_LITE_LIBRARY) + set(protobuf ${PROTOBUF_LIBRARY}) +endif(PROTOBUF_LITE_LIBRARY) + target_link_libraries(clementine-spotifyblob-messages - ${PROTOBUF_LIBRARY} + ${protobuf} ${CMAKE_THREAD_LIBS_INIT} ) - -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} -) - -target_link_libraries(clementine-spotifyblob - ${SPOTIFY_LIBRARIES} - ${QT_LIBRARIES} - clementine-spotifyblob-messages -) diff --git a/spotifyblob/common/blobversion.h.in b/spotifyblob/common/blobversion.h.in new file mode 100644 index 000000000..0dcf6bb3b --- /dev/null +++ b/spotifyblob/common/blobversion.h.in @@ -0,0 +1,23 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#ifndef SPOTIFY_BLOBVERSION_H_IN +#define SPOTIFY_BLOBVERSION_H_IN + +#define SPOTIFY_BLOB_VERSION ${SPOTIFY_BLOB_VERSION} + +#endif // SPOTIFY_BLOBVERSION_H_IN diff --git a/spotifyblob/spotifymessagehandler.cpp b/spotifyblob/common/spotifymessagehandler.cpp similarity index 95% rename from spotifyblob/spotifymessagehandler.cpp rename to spotifyblob/common/spotifymessagehandler.cpp index 4a84850e2..72e8867b7 100644 --- a/spotifyblob/spotifymessagehandler.cpp +++ b/spotifyblob/common/spotifymessagehandler.cpp @@ -58,7 +58,6 @@ void SpotifyMessageHandler::DeviceReadyRead() { return; } - qLog(Debug) << message.DebugString().c_str(); emit MessageArrived(message); // Clear the buffer @@ -71,8 +70,6 @@ void SpotifyMessageHandler::DeviceReadyRead() { } void SpotifyMessageHandler::SendMessage(const protobuf::SpotifyMessage& message) { - qLog(Debug) << message.DebugString().c_str(); - std::string data(message.SerializeAsString()); QDataStream s(device_); diff --git a/spotifyblob/spotifymessagehandler.h b/spotifyblob/common/spotifymessagehandler.h similarity index 100% rename from spotifyblob/spotifymessagehandler.h rename to spotifyblob/common/spotifymessagehandler.h diff --git a/spotifyblob/spotifymessages.proto b/spotifyblob/common/spotifymessages.proto similarity index 98% rename from spotifyblob/spotifymessages.proto rename to spotifyblob/common/spotifymessages.proto index aa09d9111..524573534 100644 --- a/spotifyblob/spotifymessages.proto +++ b/spotifyblob/common/spotifymessages.proto @@ -21,6 +21,9 @@ package protobuf; +option optimize_for = LITE_RUNTIME; + + message LoginRequest { required string username = 1; required string password = 2; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ddcc19e1..622e40c2f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -624,6 +624,7 @@ endif(HAVE_LIBLASTFM) if(HAVE_SPOTIFY) list(APPEND SOURCES + radio/spotifyblobdownloader.cpp radio/spotifyconfig.cpp radio/spotifyserver.cpp radio/spotifyservice.cpp @@ -631,6 +632,7 @@ if(HAVE_SPOTIFY) radio/spotifyurlhandler.cpp ) list(APPEND HEADERS + radio/spotifyblobdownloader.h radio/spotifyconfig.h radio/spotifyserver.h radio/spotifyservice.h @@ -1023,8 +1025,6 @@ endif(HAVE_BREAKPAD) if(HAVE_SPOTIFY) target_link_libraries(clementine_lib clementine-spotifyblob-messages) - add_dependencies(clementine_lib - clementine-spotifyblob) endif(HAVE_SPOTIFY) if (APPLE) diff --git a/src/config.h.in b/src/config.h.in index 3eba0d2e3..8f7839ec2 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -18,6 +18,7 @@ #define CONFIG_H_IN #define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" +#define CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}" #cmakedefine ENABLE_VISUALISATIONS #cmakedefine HAVE_BREAKPAD diff --git a/src/core/logging.cpp b/src/core/logging.cpp index 330ea07b0..9b2885902 100644 --- a/src/core/logging.cpp +++ b/src/core/logging.cpp @@ -34,7 +34,7 @@ static Level sDefaultLevel = Level_Debug; static QMap* sClassLevels = NULL; static QIODevice* sNullDevice = NULL; -const char* kDefaultLogLevels = "GstEnginePipeline:2,SpotifyMessageHandler:2,*:3"; +const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3"; static const char* kMessageHandlerMagic = "__logging_message__"; static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic); diff --git a/src/core/utilities.cpp b/src/core/utilities.cpp index 78bdd84e8..d5515198b 100644 --- a/src/core/utilities.cpp +++ b/src/core/utilities.cpp @@ -273,6 +273,9 @@ QString GetConfigPath(ConfigPath config) { return QDir::homePath(); #endif + case Path_LocalSpotifyBlob: + return GetConfigPath(Path_Root) + "/spotifyblob"; + default: qFatal("%s", Q_FUNC_INFO); return QString::null; diff --git a/src/core/utilities.h b/src/core/utilities.h index dd9e4ee3a..8c1116195 100644 --- a/src/core/utilities.h +++ b/src/core/utilities.h @@ -55,6 +55,7 @@ namespace Utilities { Path_GstreamerRegistry, Path_DefaultMusicLibrary, Path_Scripts, + Path_LocalSpotifyBlob, }; QString GetConfigPath(ConfigPath config); } diff --git a/src/radio/spotifyblobdownloader.cpp b/src/radio/spotifyblobdownloader.cpp new file mode 100644 index 000000000..3c1e27a9a --- /dev/null +++ b/src/radio/spotifyblobdownloader.cpp @@ -0,0 +1,137 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#include "spotifyblobdownloader.h" +#include "spotifyservice.h" +#include "core/logging.h" +#include "core/network.h" + +#include +#include +#include +#include + +SpotifyBlobDownloader::SpotifyBlobDownloader( + const QString& version, const QString& path, QObject* parent) + : QObject(parent), + version_(version), + path_(path), + network_(new NetworkAccessManager(this)), + progress_(new QProgressDialog(tr("Downloading Spotify plugin"), tr("Cancel"), 0, 0)) +{ + progress_->setWindowTitle(QCoreApplication::applicationName()); + connect(progress_, SIGNAL(canceled()), SLOT(Cancel())); +} + +SpotifyBlobDownloader::~SpotifyBlobDownloader() { + qDeleteAll(replies_); + replies_.clear(); + + delete progress_; +} + +void SpotifyBlobDownloader::Start() { + qDeleteAll(replies_); + replies_.clear(); + + const QStringList filenames = QStringList() << "blob" << "libspotify.so.7"; + + foreach (const QString& filename, filenames) { + const QUrl url(SpotifyService::kBlobDownloadUrl + version_ + "/" + filename); + qLog(Info) << "Downloading" << url; + + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + connect(reply, SIGNAL(finished()), SLOT(ReplyFinished())); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(ReplyProgress())); + + replies_ << reply; + } + + progress_->show(); +} + +void SpotifyBlobDownloader::ReplyFinished() { + QNetworkReply* reply = qobject_cast(sender()); + if (reply->error() != QNetworkReply::NoError) { + // Handle network errors + ShowError(reply->errorString()); + return; + } + + // Is everything finished? + foreach (QNetworkReply* reply, replies_) { + if (!reply->isFinished()) { + return; + } + } + + // Make the destination directory and write the files into it + QDir().mkpath(path_); + + foreach (QNetworkReply* reply, replies_) { + const QString filename = reply->url().path().section('/', -1, -1); + const QString path = path_ + "/" + filename; + + qLog(Info) << "Saving file" << path; + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) { + ShowError("Failed to open file for writing: " + path); + return; + } + + file.setPermissions(QFile::Permissions(0x7755)); + file.write(reply->readAll()); + } + + EmitFinished(); +} + +void SpotifyBlobDownloader::ReplyProgress() { + int progress = 0; + int total = 0; + + foreach (QNetworkReply* reply, replies_) { + progress += reply->bytesAvailable(); + total += reply->rawHeader("Content-Length").toInt(); + } + + progress_->setMaximum(total); + progress_->setValue(progress); +} + +void SpotifyBlobDownloader::Cancel() { + deleteLater(); +} + +void SpotifyBlobDownloader::ShowError(const QString& message) { + // Stop any remaining replies before showing the dialog so they don't + // carry on in the background + foreach (QNetworkReply* reply, replies_) { + disconnect(reply, 0, this, 0); + reply->abort(); + } + + qLog(Warning) << message; + QMessageBox::warning(NULL, tr("Error downloading Spotify plugin"), message, + QMessageBox::Close); + deleteLater(); +} + +void SpotifyBlobDownloader::EmitFinished() { + emit Finished(); + deleteLater(); +} diff --git a/src/radio/spotifyblobdownloader.h b/src/radio/spotifyblobdownloader.h new file mode 100644 index 000000000..f69c58778 --- /dev/null +++ b/src/radio/spotifyblobdownloader.h @@ -0,0 +1,59 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#ifndef SPOTIFYBLOBDOWNLOADER_H +#define SPOTIFYBLOBDOWNLOADER_H + +#include + +class QNetworkAccessManager; +class QNetworkReply; +class QProgressDialog; + +class SpotifyBlobDownloader : public QObject { + Q_OBJECT + +public: + SpotifyBlobDownloader(const QString& version, const QString& path, + QObject* parent = 0); + ~SpotifyBlobDownloader(); + + void Start(); + +signals: + void Finished(); + +private slots: + void ReplyFinished(); + void ReplyProgress(); + void Cancel(); + +private: + void ShowError(const QString& message); + void EmitFinished(); + +private: + QString version_; + QString path_; + + QNetworkAccessManager* network_; + QList replies_; + + QProgressDialog* progress_; +}; + +#endif // SPOTIFYBLOBDOWNLOADER_H diff --git a/src/radio/spotifyserver.cpp b/src/radio/spotifyserver.cpp index 4ac3e4c59..7d34f1764 100644 --- a/src/radio/spotifyserver.cpp +++ b/src/radio/spotifyserver.cpp @@ -18,8 +18,8 @@ #include "spotifyserver.h" #include "core/logging.h" -#include "spotifyblob/spotifymessages.pb.h" -#include "spotifyblob/spotifymessagehandler.h" +#include "spotifyblob/common/spotifymessages.pb.h" +#include "spotifyblob/common/spotifymessagehandler.h" #include #include diff --git a/src/radio/spotifyserver.h b/src/radio/spotifyserver.h index 9bb2aa0b2..a97e888f8 100644 --- a/src/radio/spotifyserver.h +++ b/src/radio/spotifyserver.h @@ -18,7 +18,7 @@ #ifndef SPOTIFYSERVER_H #define SPOTIFYSERVER_H -#include "spotifyblob/spotifymessages.pb.h" +#include "spotifyblob/common/spotifymessages.pb.h" #include #include diff --git a/src/radio/spotifyservice.cpp b/src/radio/spotifyservice.cpp index cd1319214..04d115e4e 100644 --- a/src/radio/spotifyservice.cpp +++ b/src/radio/spotifyservice.cpp @@ -1,4 +1,6 @@ +#include "config.h" #include "radiomodel.h" +#include "spotifyblobdownloader.h" #include "spotifyserver.h" #include "spotifyservice.h" #include "spotifysearchplaylisttype.h" @@ -7,20 +9,26 @@ #include "core/logging.h" #include "core/player.h" #include "core/taskmanager.h" +#include "core/utilities.h" #include "playlist/playlist.h" #include "playlist/playlistcontainer.h" #include "playlist/playlistmanager.h" -#include "spotifyblob/spotifymessagehandler.h" +#include "spotifyblob/common/blobversion.h" +#include "spotifyblob/common/spotifymessagehandler.h" #include "widgets/didyoumean.h" #include "ui/iconloader.h" #include +#include +#include #include +#include #include #include const char* SpotifyService::kServiceName = "Spotify"; const char* SpotifyService::kSettingsGroup = "Spotify"; +const char* SpotifyService::kBlobDownloadUrl = "http://spotify.clementine-player.org/"; const int SpotifyService::kSearchDelayMsec = 400; SpotifyService::SpotifyService(RadioModel* parent) @@ -35,12 +43,18 @@ SpotifyService::SpotifyService(RadioModel* parent) pending_search_playlist_(NULL), context_menu_(NULL), search_delay_(new QTimer(this)) { -#ifdef Q_OS_DARWIN - blob_path_ = QCoreApplication::applicationDirPath() + "/../PlugIns/clementine-spotifyblob"; -#else - blob_path_ = QCoreApplication::applicationFilePath() + "-spotifyblob"; -#endif - qLog(Debug) << "Loading spotify blob from:" << blob_path_; + // Build the search path for the binary blob. + // Look for one distributed alongside clementine first, then check in the + // user's home directory for any that have been downloaded. + blob_path_ << QCoreApplication::applicationFilePath() + "-spotifyblob" CMAKE_EXECUTABLE_SUFFIX + << QCoreApplication::applicationDirPath() + "/../PlugIns/clementine-spotifyblob" CMAKE_EXECUTABLE_SUFFIX; + + local_blob_version_ = QString("version%1-%2bit").arg(SPOTIFY_BLOB_VERSION).arg(sizeof(void*) * 8); + local_blob_path_ = Utilities::GetConfigPath(Utilities::Path_LocalSpotifyBlob) + + "/" + local_blob_version_ + "/blob"; + + qLog(Debug) << "Spotify blob search path:" << blob_path_; + qLog(Debug) << "Spotify local blob path:" << local_blob_path_; model()->player()->RegisterUrlHandler(url_handler_); model()->player()->playlists()->RegisterSpecialPlaylistType( @@ -115,19 +129,18 @@ void SpotifyService::LoginCompleted(bool success) { void SpotifyService::BlobProcessError(QProcess::ProcessError error) { qLog(Error) << "Spotify blob process failed:" << error; + blob_process_->deleteLater(); + blob_process_ = NULL; } void SpotifyService::EnsureServerCreated(const QString& username, const QString& password) { - if (server_) { + if (server_ && blob_process_) { return; } - qLog(Debug) << Q_FUNC_INFO; - + delete server_; server_ = new SpotifyServer(this); - blob_process_ = new QProcess(this); - blob_process_->setProcessChannelMode(QProcess::ForwardedChannels); connect(server_, SIGNAL(LoginCompleted(bool)), SLOT(LoginCompleted(bool))); connect(server_, SIGNAL(PlaylistsUpdated(protobuf::Playlists)), @@ -145,13 +158,7 @@ void SpotifyService::EnsureServerCreated(const QString& username, connect(server_, SIGNAL(ImageLoaded(QString,QImage)), SLOT(ImageLoaded(QString,QImage))); - connect(blob_process_, - SIGNAL(error(QProcess::ProcessError)), - SLOT(BlobProcessError(QProcess::ProcessError))); - server_->Init(); - blob_process_->start( - blob_path_, QStringList() << QString::number(server_->server_port())); login_task_id_ = model()->task_manager()->StartTask(tr("Connecting to Spotify")); @@ -163,6 +170,71 @@ void SpotifyService::EnsureServerCreated(const QString& username, } else { server_->Login(username, password); } + + StartBlobProcess(); +} + +void SpotifyService::StartBlobProcess() { + // Try to find an executable to run + QString blob_path; + QProcessEnvironment env(QProcessEnvironment::systemEnvironment()); + + // Look in the system search path first + foreach (const QString& path, blob_path_) { + if (QFile::exists(path)) { + blob_path = path; + break; + } + } + + // Next look in the local path + const QString local_blob_dir = QFileInfo(local_blob_path_).path(); + if (blob_path.isEmpty()) { + if (QFile::exists(local_blob_path_)) { + blob_path = local_blob_path_; + env.insert("LD_LIBRARY_PATH", local_blob_dir); + } + } + + if (blob_path.isEmpty()) { + // If the blob still wasn't found then we'll prompt the user to download one + if (login_task_id_) { + model()->task_manager()->SetTaskFinished(login_task_id_); + } + + #ifdef Q_OS_LINUX + QMessageBox::StandardButton ret = QMessageBox::question(NULL, + tr("Spotify plugin not installed"), + tr("An additional plugin is required to use Spotify in Clementine. Would you like to download and install it now?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (ret == QMessageBox::Yes) { + // The downloader deletes itself when it finishes + SpotifyBlobDownloader* downloader = new SpotifyBlobDownloader( + local_blob_version_, local_blob_dir, this); + connect(downloader, SIGNAL(Finished()), SLOT(BlobDownloadFinished())); + downloader->Start(); + } + #endif + + return; + } + + delete blob_process_; + blob_process_ = new QProcess(this); + blob_process_->setProcessChannelMode(QProcess::ForwardedChannels); + + connect(blob_process_, + SIGNAL(error(QProcess::ProcessError)), + SLOT(BlobProcessError(QProcess::ProcessError))); + + qLog(Info) << "Starting" << blob_path; + blob_process_->start( + blob_path, QStringList() << QString::number(server_->server_port())); +} + +void SpotifyService::BlobDownloadFinished() { + EnsureServerCreated(); } void SpotifyService::PlaylistsUpdated(const protobuf::Playlists& response) { diff --git a/src/radio/spotifyservice.h b/src/radio/spotifyservice.h index 60a407d2c..f59355673 100644 --- a/src/radio/spotifyservice.h +++ b/src/radio/spotifyservice.h @@ -3,7 +3,7 @@ #include "radiomodel.h" #include "radioservice.h" -#include "spotifyblob/spotifymessages.pb.h" +#include "spotifyblob/common/spotifymessages.pb.h" #include #include @@ -37,6 +37,7 @@ public: static const char* kServiceName; static const char* kSettingsGroup; + static const char* kBlobDownloadUrl; static const int kSearchDelayMsec; QStandardItem* CreateRootItem(); @@ -61,6 +62,7 @@ protected: private: void EnsureServerCreated(const QString& username = QString(), const QString& password = QString()); + void StartBlobProcess(); void FillPlaylist(QStandardItem* item, const protobuf::LoadPlaylistResponse& response); void SongFromProtobuf(const protobuf::Track& track, Song* song) const; void EnsureMenuCreated(); @@ -82,12 +84,15 @@ private slots: void DoSearch(); void ShowConfig(); + void BlobDownloadFinished(); private: SpotifyServer* server_; SpotifyUrlHandler* url_handler_; - QString blob_path_; + QString local_blob_version_; + QString local_blob_path_; + QStringList blob_path_; QProcess* blob_process_; QStandardItem* root_;