Remove SoundCloud support

Closes #5766, closes #5538, closes #6114, closes #5914
This commit is contained in:
John Maguire 2018-10-01 17:46:24 +01:00
parent 25ef0ff211
commit 5511583966
10 changed files with 0 additions and 1088 deletions

View File

@ -153,7 +153,6 @@ set(SOURCES
globalsearch/simplesearchprovider.cpp
globalsearch/somafmsearchprovider.cpp
globalsearch/intergalacticfmsearchprovider.cpp
globalsearch/soundcloudsearchprovider.cpp
globalsearch/spotifysearchprovider.cpp
globalsearch/suggestionwidget.cpp
globalsearch/urlsearchprovider.cpp
@ -191,8 +190,6 @@ set(SOURCES
internet/somafm/somafmurlhandler.cpp
internet/intergalacticfm/intergalacticfmservice.cpp
internet/intergalacticfm/intergalacticfmurlhandler.cpp
internet/soundcloud/soundcloudservice.cpp
internet/soundcloud/soundcloudsettingspage.cpp
internet/spotify/spotifyserver.cpp
internet/spotify/spotifyservice.cpp
internet/spotify/spotifysettingspage.cpp
@ -465,7 +462,6 @@ set(HEADERS
globalsearch/globalsearchview.h
globalsearch/searchprovider.h
globalsearch/simplesearchprovider.h
globalsearch/soundcloudsearchprovider.h
globalsearch/spotifysearchprovider.h
globalsearch/suggestionwidget.h
@ -499,8 +495,6 @@ set(HEADERS
internet/somafm/somafmurlhandler.h
internet/intergalacticfm/intergalacticfmservice.h
internet/intergalacticfm/intergalacticfmurlhandler.h
internet/soundcloud/soundcloudservice.h
internet/soundcloud/soundcloudsettingspage.h
internet/spotify/spotifyserver.h
internet/spotify/spotifyservice.h
internet/spotify/spotifysettingspage.h
@ -701,7 +695,6 @@ set(UI
internet/magnatune/magnatunedownloaddialog.ui
internet/magnatune/magnatunesettingspage.ui
internet/core/searchboxwidget.ui
internet/soundcloud/soundcloudsettingspage.ui
internet/spotify/spotifysettingspage.ui
internet/subsonic/subsonicsettingspage.ui

View File

@ -1,91 +0,0 @@
/* This file is part of Clementine.
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.
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 "soundcloudsearchprovider.h"
#include <QIcon>
#include "core/application.h"
#include "core/logging.h"
#include "covers/albumcoverloader.h"
#include "internet/soundcloud/soundcloudservice.h"
#include "ui/iconloader.h"
SoundCloudSearchProvider::SoundCloudSearchProvider(Application* app,
QObject* parent)
: SearchProvider(app, parent), service_(nullptr) {}
void SoundCloudSearchProvider::Init(SoundCloudService* service) {
service_ = service;
SearchProvider::Init(
"SoundCloud", "soundcloud", IconLoader::Load("soundcloud",
IconLoader::Provider), WantsDelayedQueries | ArtIsProbablyRemote |
CanShowConfig);
connect(service_, SIGNAL(SimpleSearchResults(int, SongList)),
SLOT(SearchDone(int, SongList)));
cover_loader_options_.desired_height_ = kArtHeight;
cover_loader_options_.pad_output_image_ = true;
cover_loader_options_.scale_output_image_ = true;
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)),
SLOT(AlbumArtLoaded(quint64, QImage)));
}
void SoundCloudSearchProvider::SearchAsync(int id, const QString& query) {
const int service_id = service_->SimpleSearch(query);
pending_searches_[service_id] = PendingState(id, TokenizeQuery(query));
;
}
void SoundCloudSearchProvider::SearchDone(int id, const SongList& songs) {
// Map back to the original id.
const PendingState state = pending_searches_.take(id);
const int global_search_id = state.orig_id_;
ResultList ret;
for (const Song& song : songs) {
Result result(this);
result.metadata_ = song;
ret << result;
}
emit ResultsAvailable(global_search_id, ret);
MaybeSearchFinished(global_search_id);
}
void SoundCloudSearchProvider::MaybeSearchFinished(int id) {
if (pending_searches_.keys(PendingState(id, QStringList())).isEmpty()) {
emit SearchFinished(id);
}
}
void SoundCloudSearchProvider::LoadArtAsync(int id, const Result& result) {
quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(
cover_loader_options_, result.metadata_);
cover_loader_tasks_[loader_id] = id;
}
void SoundCloudSearchProvider::AlbumArtLoaded(quint64 id, const QImage& image) {
if (!cover_loader_tasks_.contains(id)) {
return;
}
int original_id = cover_loader_tasks_.take(id);
emit ArtLoaded(original_id, image);
}

View File

@ -1,53 +0,0 @@
/* This file is part of Clementine.
Copyright 2012, 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 SOUNDCLOUDSEARCHPROVIDER_H
#define SOUNDCLOUDSEARCHPROVIDER_H
#include "searchprovider.h"
#include "covers/albumcoverloaderoptions.h"
#include "internet/soundcloud/soundcloudservice.h"
class AlbumCoverLoader;
class SoundCloudSearchProvider : public SearchProvider {
Q_OBJECT
public:
explicit SoundCloudSearchProvider(Application* app, QObject* parent = nullptr);
void Init(SoundCloudService* service);
// SearchProvider
void SearchAsync(int id, const QString& query);
void LoadArtAsync(int id, const Result& result);
InternetService* internet_service() { return service_; }
private slots:
void AlbumArtLoaded(quint64 id, const QImage& image);
void SearchDone(int id, const SongList& songs);
private:
void MaybeSearchFinished(int id);
SoundCloudService* service_;
QMap<int, PendingState> pending_searches_;
AlbumCoverLoaderOptions cover_loader_options_;
QMap<quint64, int> cover_loader_tasks_;
};
#endif

View File

@ -41,7 +41,6 @@
#include "internet/magnatune/magnatuneservice.h"
#include "internet/podcasts/podcastservice.h"
#include "internet/somafm/somafmservice.h"
#include "internet/soundcloud/soundcloudservice.h"
#include "internet/spotify/spotifyservice.h"
#include "internet/subsonic/subsonicservice.h"
#include "smartplaylists/generatormimedata.h"
@ -93,7 +92,6 @@ InternetModel::InternetModel(Application* app, QObject* parent)
AddService(new RadioTunesService(app, this));
AddService(new SomaFMService(app, this));
AddService(new IntergalacticFMService(app, this));
AddService(new SoundCloudService(app, this));
AddService(new SpotifyService(app, this));
AddService(new SubsonicService(app, this));
#ifdef HAVE_BOX

View File

@ -1,549 +0,0 @@
/* This file is part of Clementine.
Copyright 2012, 2014, Arnaud Bienner <arnaud.bienner@gmail.com>
Copyright 2014, maximko <me@maximko.org>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.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 "soundcloudservice.h"
#include <QDesktopServices>
#include <QMenu>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <qjson/parser.h>
#include <qjson/serializer.h>
#include "internet/core/internetmodel.h"
#include "internet/core/oauthenticator.h"
#include "internet/core/searchboxwidget.h"
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/mergedproxymodel.h"
#include "core/network.h"
#include "core/song.h"
#include "core/taskmanager.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#include "globalsearch/globalsearch.h"
#include "globalsearch/soundcloudsearchprovider.h"
#include "ui/iconloader.h"
const char* SoundCloudService::kApiClientId =
"2add0f709fcfae1fd7a198ec7573d2d4";
const char* SoundCloudService::kApiClientSecret =
"d1cd7829da2e98e1e0621d85d57a2077";
const char* SoundCloudService::kServiceName = "SoundCloud";
const char* SoundCloudService::kSettingsGroup = "SoundCloud";
const char* SoundCloudService::kUrl = "https://api.soundcloud.com/";
const char* SoundCloudService::kOAuthEndpoint = "https://soundcloud.com/connect";
const char* SoundCloudService::kOAuthTokenEndpoint = "https://api.soundcloud.com/oauth2/token";
const char* SoundCloudService::kOAuthScope = "non-expiring";
const char* SoundCloudService::kHomepage = "http://soundcloud.com/";
const int SoundCloudService::kSearchDelayMsec = 400;
const int SoundCloudService::kSongSearchLimit = 100;
const int SoundCloudService::kSongSimpleSearchLimit = 100;
typedef QPair<QString, QString> Param;
SoundCloudService::SoundCloudService(Application* app, InternetModel* parent)
: InternetService(kServiceName, app, parent, parent),
root_(nullptr),
search_(nullptr),
user_tracks_(nullptr),
user_playlists_(nullptr),
user_activities_(nullptr),
user_favorites_(nullptr),
network_(new NetworkAccessManager(this)),
context_menu_(nullptr),
search_box_(new SearchBoxWidget(this)),
search_delay_(new QTimer(this)),
next_pending_search_id_(0) {
search_delay_->setInterval(kSearchDelayMsec);
search_delay_->setSingleShot(true);
connect(search_delay_, SIGNAL(timeout()), SLOT(DoSearch()));
SoundCloudSearchProvider* search_provider =
new SoundCloudSearchProvider(app_, this);
search_provider->Init(this);
app_->global_search()->AddProvider(search_provider);
connect(search_box_, SIGNAL(TextChanged(QString)), SLOT(Search(QString)));
}
SoundCloudService::~SoundCloudService() {}
QStandardItem* SoundCloudService::CreateRootItem() {
root_ = new QStandardItem(IconLoader::Load("soundcloud",
IconLoader::Provider), kServiceName);
root_->setData(true, InternetModel::Role_CanLazyLoad);
root_->setData(InternetModel::PlayBehaviour_DoubleClickAction,
InternetModel::Role_PlayBehaviour);
return root_;
}
void SoundCloudService::LazyPopulate(QStandardItem* item) {
switch (item->data(InternetModel::Role_Type).toInt()) {
case InternetModel::Type_Service: {
EnsureItemsCreated();
break;
}
default:
break;
}
}
void SoundCloudService::EnsureItemsCreated() {
if (!search_) {
search_ =
new QStandardItem(IconLoader::Load("edit-find", IconLoader::Base),
tr("Search results"));
search_->setToolTip(
tr("Start typing something on the search box above to "
"fill this search results list"));
search_->setData(InternetModel::PlayBehaviour_MultipleItems,
InternetModel::Role_PlayBehaviour);
root_->appendRow(search_);
}
if (!user_tracks_ && !user_activities_ && !user_playlists_ && IsLoggedIn()) {
user_activities_ = new QStandardItem(tr("Activities stream"));
user_activities_->setData(InternetModel::PlayBehaviour_MultipleItems,
InternetModel::Role_PlayBehaviour);
root_->appendRow(user_activities_);
user_playlists_ = new QStandardItem(tr("Playlists"));
root_->appendRow(user_playlists_);
user_tracks_ = new QStandardItem(tr("Tracks"));
user_tracks_->setData(InternetModel::PlayBehaviour_MultipleItems,
InternetModel::Role_PlayBehaviour);
root_->appendRow(user_tracks_);
user_favorites_ = new QStandardItem(tr("Favorites"));
root_->appendRow(user_favorites_);
RetrieveUserData(); // at least, try to (this will do nothing if user isn't
// logged)
}
}
QWidget* SoundCloudService::HeaderWidget() const { return search_box_; }
void SoundCloudService::ShowConfig() {
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_SoundCloud);
}
void SoundCloudService::Homepage() {
QDesktopServices::openUrl(QUrl(kHomepage));
}
void SoundCloudService::Connect() {
OAuthenticator* oauth = new OAuthenticator(
kApiClientId, kApiClientSecret,
OAuthenticator::RedirectStyle::REMOTE_WITH_STATE, this);
oauth->StartAuthorisation(kOAuthEndpoint, kOAuthTokenEndpoint, kOAuthScope);
NewClosure(oauth, SIGNAL(Finished()), this,
SLOT(ConnectFinished(OAuthenticator*)), oauth);
}
void SoundCloudService::ConnectFinished(OAuthenticator* oauth) {
oauth->deleteLater();
access_token_ = oauth->access_token();
if (!access_token_.isEmpty()) {
emit Connected();
}
expiry_time_ = oauth->expiry_time();
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("access_token", access_token_);
EnsureItemsCreated();
}
void SoundCloudService::LoadAccessTokenIfEmpty() {
if (access_token_.isEmpty()) {
QSettings s;
s.beginGroup(kSettingsGroup);
if (!s.contains("access_token")) {
return;
}
access_token_ = s.value("access_token").toString();
}
}
bool SoundCloudService::IsLoggedIn() {
LoadAccessTokenIfEmpty();
return !access_token_.isEmpty();
}
void SoundCloudService::Logout() {
QSettings s;
s.beginGroup(kSettingsGroup);
access_token_.clear();
s.remove("access_token");
pending_playlists_requests_.clear();
if (user_activities_) root_->removeRow(user_activities_->row());
if (user_tracks_) root_->removeRow(user_tracks_->row());
if (user_playlists_) root_->removeRow(user_playlists_->row());
user_activities_ = nullptr;
user_tracks_ = nullptr;
user_playlists_ = nullptr;
}
void SoundCloudService::RetrieveUserData() {
LoadAccessTokenIfEmpty();
RetrieveUserActivities();
RetrieveUserTracks();
RetrieveUserPlaylists();
RetrieveUserFavorites();
}
void SoundCloudService::RetrieveUserTracks() {
QList<Param> parameters;
parameters << Param("oauth_token", access_token_);
QNetworkReply* reply = CreateRequest("me/tracks", parameters);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(UserTracksRetrieved(QNetworkReply*)), reply);
}
void SoundCloudService::UserTracksRetrieved(QNetworkReply* reply) {
reply->deleteLater();
SongList songs = ExtractSongs(ExtractResult(reply));
// Fill results list
for (const Song& song : songs) {
QStandardItem* child = CreateSongItem(song);
user_tracks_->appendRow(child);
}
}
void SoundCloudService::RetrieveUserActivities() {
QList<Param> parameters;
parameters << Param("oauth_token", access_token_);
QNetworkReply* reply = CreateRequest("me/activities", parameters);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(UserActivitiesRetrieved(QNetworkReply*)), reply);
}
void SoundCloudService::UserActivitiesRetrieved(QNetworkReply* reply) {
reply->deleteLater();
QList<QStandardItem*> activities = ExtractActivities(ExtractResult(reply));
// Fill results list
for (QStandardItem* activity : activities) {
user_activities_->appendRow(activity);
}
}
void SoundCloudService::RetrieveUserPlaylists() {
QList<Param> parameters;
parameters << Param("oauth_token", access_token_);
QNetworkReply* reply = CreateRequest("me/playlists", parameters);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(UserPlaylistsRetrieved(QNetworkReply*)), reply);
}
void SoundCloudService::RetrieveUserFavorites() {
QList<Param> parameters;
parameters << Param("oauth_token", access_token_);
QNetworkReply* reply = CreateRequest("me/favorites", parameters);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(UserFavoritesRetrieved(QNetworkReply*)), reply);
}
void SoundCloudService::UserPlaylistsRetrieved(QNetworkReply* reply) {
reply->deleteLater();
QList<QVariant> playlists = ExtractResult(reply).toList();
for (const QVariant& playlist : playlists) {
QMap<QString, QVariant> playlist_map = playlist.toMap();
QStandardItem* playlist_item = CreatePlaylistItem(playlist_map["title"].toString());
SongList songs = ExtractSongs(playlist_map["tracks"]);
for (const Song& song : songs) {
playlist_item->appendRow(CreateSongItem(song));
}
user_playlists_->appendRow(playlist_item);
}
}
void SoundCloudService::UserFavoritesRetrieved(QNetworkReply* reply) {
reply->deleteLater();
SongList songs = ExtractSongs(ExtractResult(reply));
// Fill results list
for (const Song& song : songs) {
QStandardItem* child = CreateSongItem(song);
user_favorites_->appendRow(child);
}
}
void SoundCloudService::Search(const QString& text, bool now) {
pending_search_ = text;
// If there is no text (e.g. user cleared search box), we don't need to do a
// real query that will return nothing: we can clear the playlist now
if (text.isEmpty()) {
search_delay_->stop();
ClearSearchResults();
return;
}
if (now) {
search_delay_->stop();
DoSearch();
} else {
search_delay_->start();
}
}
void SoundCloudService::DoSearch() {
ClearSearchResults();
QList<Param> parameters;
parameters << Param("q", pending_search_) << Param("limit", QString::number(kSongSearchLimit));
QNetworkReply* reply = CreateRequest("tracks", parameters);
const int id = next_pending_search_id_++;
NewClosure(reply, SIGNAL(finished()), this,
SLOT(SearchFinished(QNetworkReply*, int)), reply, id);
}
void SoundCloudService::SearchFinished(QNetworkReply* reply, int task_id) {
reply->deleteLater();
SongList songs = ExtractSongs(ExtractResult(reply));
// Fill results list
for (const Song& song : songs) {
QStandardItem* child = CreateSongItem(song);
search_->appendRow(child);
}
QModelIndex index = model()->merged_model()->mapFromSource(search_->index());
ScrollToIndex(index);
}
void SoundCloudService::ClearSearchResults() {
if (search_) {
search_->removeRows(0, search_->rowCount());
}
}
int SoundCloudService::SimpleSearch(const QString& text) {
QList<Param> parameters;
parameters << Param("q", text) << Param("limit", QString::number(kSongSimpleSearchLimit));
QNetworkReply* reply = CreateRequest("tracks", parameters);
const int id = next_pending_search_id_++;
NewClosure(reply, SIGNAL(finished()), this,
SLOT(SimpleSearchFinished(QNetworkReply*, int)), reply, id);
return id;
}
void SoundCloudService::SimpleSearchFinished(QNetworkReply* reply, int id) {
reply->deleteLater();
SongList songs = ExtractSongs(ExtractResult(reply));
emit SimpleSearchResults(id, songs);
}
void SoundCloudService::EnsureMenuCreated() {
if (!context_menu_) {
context_menu_ = new QMenu;
context_menu_->addActions(GetPlaylistActions());
context_menu_->addSeparator();
context_menu_->addAction(IconLoader::Load("download", IconLoader::Base),
tr("Open %1 in browser").arg("soundcloud.com"),
this, SLOT(Homepage()));
context_menu_->addSeparator();
context_menu_->addAction(IconLoader::Load("configure", IconLoader::Base),
tr("Configure SoundCloud..."),
this, SLOT(ShowConfig()));
}
}
void SoundCloudService::ShowContextMenu(const QPoint& global_pos) {
EnsureMenuCreated();
context_menu_->popup(global_pos);
}
QStandardItem* SoundCloudService::CreatePlaylistItem(const QString& playlist_name) {
QStandardItem* item = new QStandardItem(playlist_name);
item->setData(true, InternetModel::Role_CanLazyLoad);
item->setData(InternetModel::PlayBehaviour_MultipleItems,
InternetModel::Role_PlayBehaviour);
return item;
}
QNetworkReply* SoundCloudService::CreateRequest(const QString& ressource_name,
const QList<Param>& params) {
QUrl url(kUrl);
url.setPath(ressource_name);
url.addQueryItem("client_id", kApiClientId);
for (const Param& param : params) {
url.addQueryItem(param.first, param.second);
}
qLog(Debug) << "Request Url: " << url.toEncoded();
QNetworkRequest req(url);
req.setRawHeader("Accept", "application/json");
QNetworkReply* reply = network_->get(req);
return reply;
}
QVariant SoundCloudService::ExtractResult(QNetworkReply* reply) {
if (reply->error() != QNetworkReply::NoError) {
qLog(Error) << "Error when retrieving SoundCloud results:"
<< reply->errorString() << QString(" (%1)").arg(reply->error());
if (reply->error() == QNetworkReply::ContentAccessDenied ||
reply->error() == QNetworkReply::ContentOperationNotPermittedError ||
reply->error() == QNetworkReply::ContentNotFoundError ||
reply->error() == QNetworkReply::AuthenticationRequiredError) {
// In case of access denied errors (invalid token?) logout
Logout();
return QVariant();
}
}
QJson::Parser parser;
bool ok;
QVariant result = parser.parse(reply, &ok);
if (!ok) {
qLog(Error) << "Error while parsing SoundCloud result";
}
return result;
}
void SoundCloudService::RetrievePlaylist(int playlist_id,
QStandardItem* playlist_item) {
const int request_id = next_retrieve_playlist_id_++;
pending_playlists_requests_.insert(request_id,
PlaylistInfo(playlist_id, playlist_item));
QList<Param> parameters;
parameters << Param("oauth_token", access_token_);
QNetworkReply* reply =
CreateRequest("playlists/" + QString::number(playlist_id), parameters);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(PlaylistRetrieved(QNetworkReply*, int)), reply, request_id);
}
void SoundCloudService::PlaylistRetrieved(QNetworkReply* reply,
int request_id) {
if (!pending_playlists_requests_.contains(request_id)) return;
PlaylistInfo playlist_info = pending_playlists_requests_.take(request_id);
QVariant res = ExtractResult(reply);
SongList songs = ExtractSongs(res.toMap()["tracks"]);
for (const Song& song : songs) {
QStandardItem* child = CreateSongItem(song);
playlist_info.item_->appendRow(child);
}
}
QList<QStandardItem*> SoundCloudService::ExtractActivities(const QVariant& result) {
QList<QStandardItem*> activities;
QVariantList q_variant_list = result.toMap()["collection"].toList();
for (const QVariant& q : q_variant_list) {
QMap<QString, QVariant> activity = q.toMap();
const QString type = activity["type"].toString();
if (type == "track") {
Song song = ExtractSong(activity["origin"].toMap());
if (song.is_valid()) {
activities << CreateSongItem(song);
}
} else if (type == "playlist") {
QMap<QString, QVariant> origin_map = activity["origin"].toMap();
QStandardItem* playlist_item =
CreatePlaylistItem(origin_map["title"].toString());
activities << playlist_item;
RetrievePlaylist(origin_map["id"].toInt(), playlist_item);
}
}
return activities;
}
SongList SoundCloudService::ExtractSongs(const QVariant& result) {
SongList songs;
QVariantList q_variant_list = result.toList();
for (const QVariant& q : q_variant_list) {
Song song = ExtractSong(q.toMap());
if (song.is_valid()) {
songs << song;
}
}
return songs;
}
Song SoundCloudService::ExtractSong(const QVariantMap& result_song) {
Song song;
if (!result_song.isEmpty() && result_song["streamable"].toBool()) {
QUrl stream_url = result_song["stream_url"].toUrl();
stream_url.addQueryItem("client_id", kApiClientId);
song.set_url(stream_url);
QString username = result_song["user"].toMap()["username"].toString();
// We don't have a real artist name, but username is the most similar thing
// we have
song.set_artist(username);
QString title = result_song["title"].toString();
song.set_title(title);
QString genre = result_song["genre"].toString();
song.set_genre(genre);
float bpm = result_song["bpm"].toFloat();
song.set_bpm(bpm);
QVariant cover = result_song["artwork_url"];
if (cover.isValid()) {
// Increase cover size.
// See https://developers.soundcloud.com/docs/api/reference#artwork_url
QString big_cover = cover.toString().replace("large", "t500x500");
QUrl cover_url(big_cover, QUrl::StrictMode);
// SoundCloud covers URL are https, but our cover loader doesn't seem to
// deal well with https URL. Anyway, we don't need a secure connection to
// get a cover image.
cover_url.setScheme("http");
song.set_art_automatic(cover_url.toEncoded());
}
int playcount = result_song["playback_count"].toInt();
song.set_playcount(playcount);
int year = result_song["release_year"].toInt();
song.set_year(year);
QVariant q_duration = result_song["duration"];
quint64 duration = q_duration.toULongLong() * kNsecPerMsec;
song.set_length_nanosec(duration);
song.set_valid(true);
}
return song;
}

View File

@ -1,150 +0,0 @@
/* This file is part of Clementine.
Copyright 2012, 2014, Arnaud Bienner <arnaud.bienner@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.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 INTERNET_SOUNDCLOUD_SOUNDCLOUDSERVICE_H_
#define INTERNET_SOUNDCLOUD_SOUNDCLOUDSERVICE_H_
#include "internet/core/internetmodel.h"
#include "internet/core/internetservice.h"
class NetworkAccessManager;
class OAuthenticator;
class SearchBoxWidget;
class QMenu;
class QNetworkReply;
class SoundCloudService : public InternetService {
Q_OBJECT
public:
SoundCloudService(Application* app, InternetModel* parent);
~SoundCloudService();
// Internet Service methods
QStandardItem* CreateRootItem();
void LazyPopulate(QStandardItem* parent);
// TODO(Arnaud Bienner)
// QList<QAction*> playlistitem_actions(const Song& song);
void ShowContextMenu(const QPoint& global_pos);
QWidget* HeaderWidget() const;
void Connect();
bool IsLoggedIn();
void Logout();
int SimpleSearch(const QString& query);
static const char* kServiceName;
static const char* kSettingsGroup;
signals:
void SimpleSearchResults(int id, SongList songs);
void Connected();
public slots:
void ShowConfig();
private slots:
void ConnectFinished(OAuthenticator* oauth);
void UserTracksRetrieved(QNetworkReply* reply);
void UserActivitiesRetrieved(QNetworkReply* reply);
void UserPlaylistsRetrieved(QNetworkReply* reply);
void UserFavoritesRetrieved(QNetworkReply* reply);
void PlaylistRetrieved(QNetworkReply* reply, int request_id);
void Search(const QString& text, bool now = false);
void DoSearch();
void SearchFinished(QNetworkReply* reply, int task);
void SimpleSearchFinished(QNetworkReply* reply, int id);
void Homepage();
private:
struct PlaylistInfo {
PlaylistInfo() {}
PlaylistInfo(int id, QStandardItem* item) : id_(id), item_(item) {}
int id_;
QStandardItem* item_;
};
// Try to load "access_token" from preferences if the current access_token's
// value is empty
void LoadAccessTokenIfEmpty();
void RetrieveUserData();
void RetrieveUserTracks();
void RetrieveUserActivities();
void RetrieveUserPlaylists();
void RetrieveUserFavorites();
void RetrievePlaylist(int playlist_id, QStandardItem* playlist_item);
void ClearSearchResults();
void EnsureItemsCreated();
void EnsureMenuCreated();
QStandardItem* CreatePlaylistItem(const QString& playlist_name);
QNetworkReply* CreateRequest(const QString& ressource_name,
const QList<QPair<QString, QString>>& params);
// Convenient function for extracting result from reply
QVariant ExtractResult(QNetworkReply* reply);
// Returns items directly, as activities can be playlists or songs
QList<QStandardItem*> ExtractActivities(const QVariant& result);
SongList ExtractSongs(const QVariant& result);
Song ExtractSong(const QVariantMap& result_song);
QStandardItem* root_;
QStandardItem* search_;
QStandardItem* user_tracks_;
QStandardItem* user_playlists_;
QStandardItem* user_activities_;
QStandardItem* user_favorites_;
NetworkAccessManager* network_;
QMenu* context_menu_;
SearchBoxWidget* search_box_;
QTimer* search_delay_;
QString pending_search_;
// Request IDs
int next_pending_search_id_;
int next_retrieve_playlist_id_;
QMap<int, PlaylistInfo> pending_playlists_requests_;
QByteArray api_key_;
QString access_token_;
QDateTime expiry_time_;
static const char* kUrl;
static const char* kOAuthEndpoint;
static const char* kOAuthTokenEndpoint;
static const char* kOAuthScope;
static const char* kHomepage;
static const int kSongSearchLimit;
static const int kSongSimpleSearchLimit;
static const int kSearchDelayMsec;
static const char* kApiClientId;
static const char* kApiClientSecret;
};
#endif // INTERNET_SOUNDCLOUD_SOUNDCLOUDSERVICE_H_

View File

@ -1,77 +0,0 @@
/* This file is part of Clementine.
Copyright 2014, Arnaud Bienner <arnaud.bienner@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.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 "soundcloudservice.h"
#include "soundcloudsettingspage.h"
#include "ui_soundcloudsettingspage.h"
#include "core/application.h"
#include "internet/core/internetmodel.h"
#include "ui/iconloader.h"
SoundCloudSettingsPage::SoundCloudSettingsPage(SettingsDialog* parent)
: SettingsPage(parent),
ui_(new Ui::SoundCloudSettingsPage),
service_(
dialog()->app()->internet_model()->Service<SoundCloudService>()) {
ui_->setupUi(this);
setWindowIcon(IconLoader::Load("soundcloud", IconLoader::Provider));
ui_->login_state->AddCredentialGroup(ui_->login_container);
connect(ui_->login_button, SIGNAL(clicked()), SLOT(LoginClicked()));
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked()));
connect(service_, SIGNAL(Connected()), SLOT(Connected()));
dialog()->installEventFilter(this);
}
SoundCloudSettingsPage::~SoundCloudSettingsPage() { delete ui_; }
void SoundCloudSettingsPage::Load() {
if (service_->IsLoggedIn()) {
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
}
}
void SoundCloudSettingsPage::Save() {
// Everything is done in the service: nothing to do here
}
void SoundCloudSettingsPage::LoginClicked() {
service_->Connect();
ui_->login_button->setEnabled(false);
}
bool SoundCloudSettingsPage::eventFilter(QObject* object, QEvent* event) {
if (object == dialog() && event->type() == QEvent::Enter) {
ui_->login_button->setEnabled(true);
return false;
}
return SettingsPage::eventFilter(object, event);
}
void SoundCloudSettingsPage::LogoutClicked() {
service_->Logout();
ui_->login_button->setEnabled(true);
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut);
}
void SoundCloudSettingsPage::Connected() {
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
}

View File

@ -1,51 +0,0 @@
/* This file is part of Clementine.
Copyright 2014, Arnaud Bienner <arnaud.bienner@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.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 INTERNET_SOUNDCLOUD_SOUNDCLOUDSETTINGSPAGE_H_
#define INTERNET_SOUNDCLOUD_SOUNDCLOUDSETTINGSPAGE_H_
#include "ui/settingspage.h"
class SoundCloudService;
class Ui_SoundCloudSettingsPage;
class SoundCloudSettingsPage : public SettingsPage {
Q_OBJECT
public:
explicit SoundCloudSettingsPage(SettingsDialog* parent = nullptr);
~SoundCloudSettingsPage();
void Load();
void Save();
// QObject
bool eventFilter(QObject* object, QEvent* event);
private slots:
void LoginClicked();
void LogoutClicked();
void Connected();
private:
Ui_SoundCloudSettingsPage* ui_;
SoundCloudService* service_;
};
#endif // INTERNET_SOUNDCLOUD_SOUNDCLOUDSETTINGSPAGE_H_

View File

@ -1,106 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SoundCloudSettingsPage</class>
<widget class="QWidget" name="SoundCloudSettingsPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>569</width>
<height>491</height>
</rect>
</property>
<property name="windowTitle">
<string>SoundCloud</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>You don't need to be logged in to search and to listen to music on SoundCloud. However, you need to login to access your playlists and your stream.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="LoginStateWidget" name="login_state" native="true"/>
</item>
<item>
<widget class="QWidget" name="login_container" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>28</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="login_button">
<property name="text">
<string>Login</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Clicking the Login button will open a web browser. You should return to Clementine after you have logged in.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</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>357</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LoginStateWidget</class>
<extends>QWidget</extends>
<header>widgets/loginstatewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<connections/>
</ui>

View File

@ -34,7 +34,6 @@
#include "internet/digitally/digitallyimportedsettingspage.h"
#include "internet/magnatune/magnatunesettingspage.h"
#include "internet/podcasts/podcastsettingspage.h"
#include "internet/soundcloud/soundcloudsettingspage.h"
#include "internet/spotify/spotifysettingspage.h"
#include "internet/subsonic/subsonicsettingspage.h"
#include "library/librarysettingspage.h"
@ -175,7 +174,6 @@ SettingsDialog::SettingsDialog(Application* app, BackgroundStreams* streams,
AddPage(Page_Skydrive, new SkydriveSettingsPage(this), providers);
#endif
AddPage(Page_SoundCloud, new SoundCloudSettingsPage(this), providers);
AddPage(Page_Spotify, new SpotifySettingsPage(this), providers);
#ifdef HAVE_SEAFILE