From a0258a9e8103176a2713ad510f54b2e7c43a5b48 Mon Sep 17 00:00:00 2001 From: David Sansome Date: Thu, 21 Jul 2011 00:03:55 +0100 Subject: [PATCH] Add c++ implementations of the di.fm and sky.fm radio services --- data/data.qrc | 3 + data/providers/digitallyimported-32.png | Bin 0 -> 1204 bytes data/providers/digitallyimported.png | Bin 0 -> 211 bytes data/providers/skyfm.png | Bin 0 -> 864 bytes src/CMakeLists.txt | 9 + src/internet/digitallyimportedconfig.cpp | 54 +++++ src/internet/digitallyimportedconfig.h | 40 ++++ src/internet/digitallyimportedconfig.ui | 162 +++++++++++++ src/internet/digitallyimportedservice.cpp | 64 +++++ src/internet/digitallyimportedservice.h | 32 +++ src/internet/digitallyimportedservicebase.cpp | 221 ++++++++++++++++++ src/internet/digitallyimportedservicebase.h | 113 +++++++++ src/internet/digitallyimportedurlhandler.cpp | 96 ++++++++ src/internet/digitallyimportedurlhandler.h | 43 ++++ src/internet/internetmodel.cpp | 18 +- src/internet/lastfmservice.cpp | 1 - src/internet/magnatuneservice.cpp | 2 - src/internet/skyfmservice.cpp | 91 ++++++++ src/internet/skyfmservice.h | 41 ++++ src/internet/spotifyservice.cpp | 15 +- src/internet/spotifyservice.h | 3 +- src/ui/settingsdialog.cpp | 4 + src/ui/settingsdialog.h | 1 + src/ui/settingsdialog.ui | 30 ++- 24 files changed, 1023 insertions(+), 20 deletions(-) create mode 100644 data/providers/digitallyimported-32.png create mode 100644 data/providers/digitallyimported.png create mode 100644 data/providers/skyfm.png create mode 100644 src/internet/digitallyimportedconfig.cpp create mode 100644 src/internet/digitallyimportedconfig.h create mode 100644 src/internet/digitallyimportedconfig.ui create mode 100644 src/internet/digitallyimportedservice.cpp create mode 100644 src/internet/digitallyimportedservice.h create mode 100644 src/internet/digitallyimportedservicebase.cpp create mode 100644 src/internet/digitallyimportedservicebase.h create mode 100644 src/internet/digitallyimportedurlhandler.cpp create mode 100644 src/internet/digitallyimportedurlhandler.h create mode 100644 src/internet/skyfmservice.cpp create mode 100644 src/internet/skyfmservice.h diff --git a/data/data.qrc b/data/data.qrc index 3d7d6dabc..11f36f9c9 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -340,5 +340,8 @@ pythonlibs/uic/uiparser.py pythonlibs/clementinelogging.py nyancat.png + providers/digitallyimported.png + providers/skyfm.png + providers/digitallyimported-32.png diff --git a/data/providers/digitallyimported-32.png b/data/providers/digitallyimported-32.png new file mode 100644 index 0000000000000000000000000000000000000000..bfef73f5c7eeef963cbd1c1ef6096afcae189368 GIT binary patch literal 1204 zcmV;l1WWsgP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01iz601li1-u9eV00007bV*G`2ipf1 z4I(Wec6@vQ000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000CJNklZYah5d z{_n#8o_XizCo2Z;Uf5ho;LWr(Z)d+Skm_H+G;K|N!~LOVQsRzVMyHpD`S6j)RzAo8 zm&QBZd;8=LFcXWn{Wph*rM0K-x$-_>8Q|y-tB?ao_8)9-Bsh9%8&KoTrBMgz1TWFy zDS8jguj?WQ!?e!ep30R=^wyocbO_04Pn8 zD-w8g4s>bPgh_6YEMd8xYs}0!5K#yjkpja(!Z0i+TmpB}Sd48U|N9P1^KY2N3l9a^ z4RsWrF(B42IbWQGug*G`M^-!T!6SQu2X;F~;p0=ri66ZyFm;ylR4!W{7;H1=r2`I> zo^vxip?-gHBwqQXaOBt$n^z6<;L&}FL-z&X{JLSBI5Qi2XJO%Rk7o0BNi$3^Pot0YpCgf{<;R;YwSS?G_v`X7)E@Wgw0nYN1O!{+994*C1Ihk;XPxVu6hiF$@E%NG>vgN$k3H z#M5_sqmqT+E(ReX^XvCCo6>>n#2$q=4mxkjPzafDU}fM?0uWcL%=hO5ser7O|<&czLI6CvZ%Gb6SSh9p&dbH0gUL46p&;JMH!G`&c{ za8R(eQ%<_2rYw{qstUq|xnm(Bi6xwz0t`weMkROgwc)(#mfOf^I$3SpFcVkC2~!Qb z`b@Q`i6`o|no^y21!myIC{|I5ks?t64T#M4wLEF-s1cF+EGRXHBP-3?9%xFFTQ9Xl zhf}A=z|4>`xCo2p9BI7(J9`;mbpY#|^}(WbaiVDQ8GiIs%<998==8q@$_#368l7rh z)INx%VQ{srlZV&-LKP^iuA&fBR$m1u-d&ZW=3P{V3X895_SaAt6jO}hz@Ci`fua5J z#KC)c`NHMFnwgV4;Wn+kHlYqw5)m=!6E~zK?Qu-l?c1x<#Kz&9|B7x7fPVlAmHuz) SY45)P0000QL70(Y)*K0-AbW|YuPgg)Mm|wZPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipM; z4<;>KbLYkY00Q7iL_t(I%LT!`b5vCrz~SF}?m71}yUA|C8e+0a)aYm!3)46TXDoE= z97n$Soc?Mzs3 zun;SS6LKdc7Gfh<1J)=;00pm#SFm+mXQ$h%=EW~u02@ntz zBL*Xa_1;re6=mfp9ry^wrRVm&L;gJ+aIuxsX(mi7&v;g`yS2j3<}wjr49K^)MrtF2 zbCx2GXe7$*zYiePe6iD|UBpBNtSCa@*ME+AGMMw#?gnB6+g;9CaEgfX;;2tT!b)zq zx^s!xMu-5wf4y`5*gxaXqjNrdV~OAQPxy4ZgPqk%Y?P;iAq>Ync=D1*r(<4erhKru zL?f|0Iv#Oz?~oYb&cTR-!IW!Pw?G8zgEE*@+DYdJ}7n^0a@(?mIb+G-c~z%CxMB1PWv5jpuaRId5%b%<6*Z z!8!Mzk9h4u4uD+Wn`y_Mv$2>V27F!eh^yiK| z7lr)ZTKr4&6(rvT3m@%&vXAM?W z2_fLU#~8!uxIz)umI}Jf79p}Y1D(RsN|emP#0!}fq5w98fQX=~c<+%)IGY&CFsG42 qGZ%_1VK5V_0F78^#$W{^QT_*C>`akuUbv + + 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 "digitallyimportedconfig.h" +#include "digitallyimportedservicebase.h" +#include "ui_digitallyimportedconfig.h" + +#include + + +DigitallyImportedConfig::DigitallyImportedConfig(QWidget *parent) + : QWidget(parent), + ui_(new Ui_DigitallyImportedConfig) +{ + ui_->setupUi(this); +} + +DigitallyImportedConfig::~DigitallyImportedConfig() { + delete ui_; +} + +void DigitallyImportedConfig::Load() { + QSettings s; + s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup); + + ui_->audio_type->setCurrentIndex(s.value("audio_type", 0).toInt()); + ui_->username->setText(s.value("username").toString()); + ui_->password->setText(s.value("password").toString()); +} + +void DigitallyImportedConfig::Save() { + QSettings s; + s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup); + + s.setValue("audio_type", ui_->audio_type->currentIndex()); + s.setValue("username", ui_->username->text()); + s.setValue("password", ui_->password->text()); +} + + diff --git a/src/internet/digitallyimportedconfig.h b/src/internet/digitallyimportedconfig.h new file mode 100644 index 000000000..d4b27b247 --- /dev/null +++ b/src/internet/digitallyimportedconfig.h @@ -0,0 +1,40 @@ +/* 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 DIGITALLYIMPORTEDCONFIG_H +#define DIGITALLYIMPORTEDCONFIG_H + +#include + +class Ui_DigitallyImportedConfig; + +class DigitallyImportedConfig : public QWidget { + Q_OBJECT + +public: + DigitallyImportedConfig(QWidget* parent = 0); + ~DigitallyImportedConfig(); + +public slots: + void Load(); + void Save(); + +private: + Ui_DigitallyImportedConfig* ui_; +}; + +#endif // DIGITALLYIMPORTEDCONFIG_H diff --git a/src/internet/digitallyimportedconfig.ui b/src/internet/digitallyimportedconfig.ui new file mode 100644 index 000000000..4e68c65af --- /dev/null +++ b/src/internet/digitallyimportedconfig.ui @@ -0,0 +1,162 @@ + + + DigitallyImportedConfig + + + + 0 + 0 + 715 + 425 + + + + Form + + + + + + + 0 + 0 + + + + Account details (Premium) + + + + + + Digitally Imported username + + + + + + + + + + Digitally Imported password + + + + + + + QLineEdit::Password + + + + + + + You can <b>listen for free</b> without an account, but Premium members can listen to <b>higher quality</b> streams without advertisements. + + + true + + + + + + + <a href="http://www.di.fm/premium/">Upgrade to Premium now</a> + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + + + + + 0 + 0 + + + + Preferences + + + + + + Audio type + + + + + + + + MP3 96k + + + + + MP3 256k (Premium only) + + + + + AAC 32k + + + + + AAC 64k (Premium only) + + + + + AAC 128k (Premium only) + + + + + Windows Media 40k + + + + + Windows Media 64k (Premium only) + + + + + Windows Media 128k (Premium only) + + + + + + + + + + + Qt::Vertical + + + + 20 + 166 + + + + + + + + + diff --git a/src/internet/digitallyimportedservice.cpp b/src/internet/digitallyimportedservice.cpp new file mode 100644 index 000000000..3f40f08ca --- /dev/null +++ b/src/internet/digitallyimportedservice.cpp @@ -0,0 +1,64 @@ +/* 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 "digitallyimportedservice.h" +#include "core/logging.h" + +#include +#include +#include + +DigitallyImportedService::DigitallyImportedService(InternetModel* model, QObject* parent) + : DigitallyImportedServiceBase( + "DigitallyImported", "Digitally Imported", QUrl("http://www.di.fm"), + "di.fm", QUrl("http://listen.di.fm"), "digitallyimported", + QIcon(":/providers/digitallyimported.png"), model, parent) +{ + playlists_ = QList() + << Playlist(false, "http://listen.di.fm/public3/%1.pls") + << Playlist(true, "http://www.di.fm/listen/%1/premium.pls") + << Playlist(false, "http://listen.di.fm/public2/%1.pls") + << Playlist(true, "http://www.di.fm/listen/%1/64k.pls") + << Playlist(true, "http://www.di.fm/listen/%1/128k.pls") + << Playlist(false, "http://listen.di.fm/public5/%1.asx") + << Playlist(true, "http://www.di.fm/listen/%1/64k.asx") + << Playlist(true, "http://www.di.fm/listen/%1/128k.asx"); +} + +void DigitallyImportedService::ReloadSettings() { + DigitallyImportedServiceBase::ReloadSettings(); + + QNetworkCookieJar* cookies = new QNetworkCookieJar; + + if (is_premium_account()) { + qLog(Debug) << "Setting premium account cookies"; + cookies->setCookiesFromUrl(QList() + << QNetworkCookie("_amember_ru", username_.toUtf8()) + << QNetworkCookie("_amember_rp", password_.toUtf8()), + QUrl("http://www.di.fm/")); + } + + network_->setCookieJar(cookies); +} + +void DigitallyImportedService::LoadStation(const QString& key) { + QUrl playlist_url(playlists_[audio_type_].url_template_.arg(key)); + qLog(Debug) << "Getting playlist URL" << playlist_url; + + QNetworkReply* reply = network_->get(QNetworkRequest(playlist_url)); + connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished())); +} diff --git a/src/internet/digitallyimportedservice.h b/src/internet/digitallyimportedservice.h new file mode 100644 index 000000000..6add8c5f7 --- /dev/null +++ b/src/internet/digitallyimportedservice.h @@ -0,0 +1,32 @@ +/* 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 DIGITALLYIMPORTEDSERVICE_H +#define DIGITALLYIMPORTEDSERVICE_H + +#include "digitallyimportedservicebase.h" + +class DigitallyImportedService : public DigitallyImportedServiceBase { +public: + DigitallyImportedService(InternetModel* model, QObject* parent = NULL); + + void ReloadSettings(); + + void LoadStation(const QString& key); +}; + +#endif // DIGITALLYIMPORTEDSERVICE_H diff --git a/src/internet/digitallyimportedservicebase.cpp b/src/internet/digitallyimportedservicebase.cpp new file mode 100644 index 000000000..f4487fb11 --- /dev/null +++ b/src/internet/digitallyimportedservicebase.cpp @@ -0,0 +1,221 @@ +/* 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 "digitallyimportedservicebase.h" +#include "digitallyimportedurlhandler.h" +#include "internetmodel.h" +#include "core/logging.h" +#include "core/network.h" +#include "core/player.h" +#include "core/taskmanager.h" +#include "ui/iconloader.h" + +#include +#include +#include +#include + +const char* DigitallyImportedServiceBase::kSettingsGroup = "digitally_imported"; + + +DigitallyImportedServiceBase::DigitallyImportedServiceBase( + const QString& name, const QString& description, const QUrl& homepage_url, + const QString& homepage_name, const QUrl& stream_list_url, + const QString& url_scheme, const QIcon& icon, + InternetModel* model, QObject* parent) + : InternetService(name, model, parent), + network_(new NetworkAccessManager(this)), + url_handler_(new DigitallyImportedUrlHandler(this)), + audio_type_(0), + task_id_(-1), + homepage_url_(homepage_url), + homepage_name_(homepage_name), + stream_list_url_(stream_list_url), + icon_(icon), + service_description_(description), + url_scheme_(url_scheme), + root_(NULL), + context_menu_(NULL), + context_item_(NULL) +{ + model->player()->RegisterUrlHandler(url_handler_); +} + +DigitallyImportedServiceBase::~DigitallyImportedServiceBase() { + delete context_menu_; +} + +QStandardItem* DigitallyImportedServiceBase::CreateRootItem() { + root_ = new QStandardItem(icon_, service_description_); + root_->setData(true, InternetModel::Role_CanLazyLoad); + return root_; +} + +void DigitallyImportedServiceBase::LazyPopulate(QStandardItem* parent) { + if (parent == root_) { + RefreshStreams(); + } +} + +void DigitallyImportedServiceBase::RefreshStreams() { + if (task_id_ != -1) { + return; + } + + qLog(Info) << "Getting stream list from" << stream_list_url_; + + // Get the list of streams + QNetworkReply* reply = network_->get(QNetworkRequest(stream_list_url_)); + connect(reply, SIGNAL(finished()), SLOT(RefreshStreamsFinished())); + + // Start a task to tell the user we're busy + task_id_ = model()->task_manager()->StartTask(tr("Getting streams")); +} + +void DigitallyImportedServiceBase::RefreshStreamsFinished() { + QNetworkReply* reply = qobject_cast(sender()); + if (!reply) { + return; + } + + model()->task_manager()->SetTaskFinished(task_id_); + reply->deleteLater(); + + const QString data = QString::fromUtf8(reply->readAll()); + + // Poor man's JSON parser that's good enough for the stream lists and means + // we don't have to pull in QJSON as a dependency. + const QRegExp re("\\{" + "\"id\":(\\d+)," + "\"key\":\"([^\"]+)\"," + "\"name\":\"([^\"]+)\"," + "\"description\":\"([^\"]+)\""); + + QList streams; + + int pos = 0; + while (pos >= 0) { + pos = re.indexIn(data, pos); + if (pos == -1) { + break; + } + pos += re.matchedLength(); + + Stream stream; + stream.id_ = re.cap(1).toInt(); + stream.key_ = re.cap(2).replace("\\/", "/"); + stream.name_ = re.cap(3).replace("\\/", "/"); + stream.description_ = re.cap(4).replace("\\/", "/"); + streams << stream; + } + + // Sort by name + qSort(streams); + + // Add each stream to the model + foreach (const Stream& stream, streams) { + Song song; + song.set_title(stream.name_); + song.set_artist(service_description_); + song.set_url(QUrl(url_scheme_ + "://" + stream.key_)); + + QStandardItem* item = new QStandardItem(QIcon(":/last.fm/icon_radio.png"), + stream.name_); + item->setData(stream.description_, Qt::ToolTipRole); + item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour); + item->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata); + root_->appendRow(item); + } +} + +void DigitallyImportedServiceBase::Homepage() { + QDesktopServices::openUrl(homepage_url_); +} + +void DigitallyImportedServiceBase::ReloadSettings() { + QSettings s; + s.beginGroup(kSettingsGroup); + + audio_type_ = s.value("audio_type", 0).toInt(); + username_ = s.value("username").toString(); + password_ = s.value("password").toString(); +} + +void DigitallyImportedServiceBase::ShowContextMenu( + const QModelIndex& index, const QPoint& global_pos) { + if (!context_menu_) { + context_menu_ = new QMenu; + context_menu_->addActions(GetPlaylistActions()); + context_menu_->addAction(IconLoader::Load("download"), + tr("Open %1 in browser").arg(homepage_name_), + this, SLOT(Homepage())); + context_menu_->addAction(IconLoader::Load("view-refresh"), + tr("Refresh streams"), + this, SLOT(RefreshStreams())); + context_menu_->addSeparator(); + context_menu_->addAction(IconLoader::Load("configure"), + tr("Configure..."), + this, SLOT(ShowSettingsDialog())); + } + + context_item_ = model()->itemFromIndex(index); + context_menu_->popup(global_pos); +} + +QModelIndex DigitallyImportedServiceBase::GetCurrentIndex() { + return context_item_->index(); +} + +bool DigitallyImportedServiceBase::is_valid_stream_selected() const { + return audio_type_ >= 0 && audio_type_ < playlists_.count(); +} + +bool DigitallyImportedServiceBase::is_premium_account() const { + return !username_.isEmpty() && !password_.isEmpty(); +} + +bool DigitallyImportedServiceBase::is_premium_stream_selected() const { + if (!is_valid_stream_selected()) { + return false; + } + + return playlists_[audio_type_].premium_; +} + +void DigitallyImportedServiceBase::LoadPlaylistFinished() { + QNetworkReply* reply = qobject_cast(sender()); + if (!reply) { + return; + } + reply->deleteLater(); + + if (reply->header(QNetworkRequest::ContentTypeHeader).toString() == "text/html") { + url_handler_->CancelTask(); + + if (is_premium_stream_selected()) { + emit StreamError(tr("Invalid di.fm username or password")); + } else { + emit StreamError(tr("Error loading di.fm playlist")); + } + } else { + url_handler_->LoadPlaylistFinished(reply); + } +} + +void DigitallyImportedServiceBase::ShowSettingsDialog() { + emit OpenSettingsAtPage(SettingsDialog::Page_DigitallyImported); +} diff --git a/src/internet/digitallyimportedservicebase.h b/src/internet/digitallyimportedservicebase.h new file mode 100644 index 000000000..9d1d559b5 --- /dev/null +++ b/src/internet/digitallyimportedservicebase.h @@ -0,0 +1,113 @@ +/* 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 DIGITALLYIMPORTEDSERVICEBASE_H +#define DIGITALLYIMPORTEDSERVICEBASE_H + +#include "internetservice.h" + +class DigitallyImportedUrlHandler; + +class QNetworkAccessManager; + + +class DigitallyImportedServiceBase : public InternetService { + Q_OBJECT + friend class DigitallyImportedUrlHandler; + +public: + DigitallyImportedServiceBase( + const QString& name, const QString& description, const QUrl& homepage_url, + const QString& homepage_name, const QUrl& stream_list_url, + const QString& url_scheme, const QIcon& icon, + InternetModel* model, QObject* parent = NULL); + ~DigitallyImportedServiceBase(); + + static const char* kSettingsGroup; + + QStandardItem* CreateRootItem(); + void LazyPopulate(QStandardItem* parent); + void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos); + + void ReloadSettings(); + + bool is_valid_stream_selected() const; + bool is_premium_stream_selected() const; + bool is_premium_account() const; + +protected: + struct Playlist { + Playlist(bool premium, const QString& url_template) + : premium_(premium), url_template_(url_template) {} + + bool premium_; + QString url_template_; + }; + + QModelIndex GetCurrentIndex(); + + // Called by DigitallyImportedUrlHandler, implemented by subclasses, must + // call LoadPlaylistFinished eventually. + virtual void LoadStation(const QString& key) = 0; + +protected slots: + void LoadPlaylistFinished(); + +private slots: + void Homepage(); + void RefreshStreams(); + void RefreshStreamsFinished(); + void ShowSettingsDialog(); + +protected: + QNetworkAccessManager* network_; + DigitallyImportedUrlHandler* url_handler_; + + int audio_type_; + QString username_; + QString password_; + + int task_id_; + + QList playlists_; + +private: + struct Stream { + int id_; + QString key_; + QString name_; + QString description_; + + bool operator <(const Stream& other) const { return name_ < other.name_; } + }; + +private: + // Set by subclasses through the constructor + QUrl homepage_url_; + QString homepage_name_; + QUrl stream_list_url_; + QIcon icon_; + QString service_description_; + QString url_scheme_; + + QStandardItem* root_; + + QMenu* context_menu_; + QStandardItem* context_item_; +}; + +#endif // DIGITALLYIMPORTEDSERVICEBASE_H diff --git a/src/internet/digitallyimportedurlhandler.cpp b/src/internet/digitallyimportedurlhandler.cpp new file mode 100644 index 000000000..cd7045879 --- /dev/null +++ b/src/internet/digitallyimportedurlhandler.cpp @@ -0,0 +1,96 @@ +/* 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 "digitallyimportedservicebase.h" +#include "digitallyimportedurlhandler.h" +#include "internetmodel.h" +#include "core/logging.h" +#include "core/taskmanager.h" +#include "playlistparsers/playlistparser.h" + +DigitallyImportedUrlHandler::DigitallyImportedUrlHandler(DigitallyImportedServiceBase* service) + : UrlHandler(service), + service_(service), + task_id_(-1) +{ +} + +QString DigitallyImportedUrlHandler::scheme() const { + return service_->url_scheme_; +} + +UrlHandler_LoadResult DigitallyImportedUrlHandler::StartLoading(const QUrl& url) { + UrlHandler_LoadResult ret(url); + if (task_id_ != -1) { + return ret; + } + + if (!service_->is_valid_stream_selected()) { + service_->StreamError(tr("You have selected an invalid audio type setting")); + return ret; + } + + if (service_->is_premium_stream_selected() && !service_->is_premium_account()) { + service_->StreamError(tr("You have selected a Premium-only audio type but do not have any account details entered")); + return ret; + } + + // Start loading the station + const QString key = url.host(); + qLog(Info) << "Loading station" << key; + service_->LoadStation(key); + + // Save the URL so we can emit it in the finished signal later + last_original_url_ = url; + + // Tell the user what's happening + task_id_ = service_->model()->task_manager()->StartTask(tr("Loading stream")); + + ret.type_ = UrlHandler_LoadResult::WillLoadAsynchronously; + return ret; +} + +void DigitallyImportedUrlHandler::LoadPlaylistFinished(QIODevice* device) { + if (task_id_ == -1) { + return; + } + + // Stop the spinner in the status bar + CancelTask(); + + // Try to parse the playlist + PlaylistParser parser(NULL); + QList songs = parser.LoadFromDevice(device); + + qLog(Info) << "Loading station finished, got" << songs.count() << "songs"; + + // Failed to get playlist? + if (songs.count() == 0) { + service_->StreamError(tr("Error loading di.fm playlist")); + return; + } + + emit AsyncLoadComplete(UrlHandler_LoadResult( + last_original_url_, + UrlHandler_LoadResult::TrackAvailable, + songs[0].url())); +} + +void DigitallyImportedUrlHandler::CancelTask() { + service_->model()->task_manager()->SetTaskFinished(task_id_); + task_id_ = -1; +} diff --git a/src/internet/digitallyimportedurlhandler.h b/src/internet/digitallyimportedurlhandler.h new file mode 100644 index 000000000..32d8a0437 --- /dev/null +++ b/src/internet/digitallyimportedurlhandler.h @@ -0,0 +1,43 @@ +/* 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 DIGITALLYIMPORTEDURLHANDLER_H +#define DIGITALLYIMPORTEDURLHANDLER_H + +#include "core/urlhandler.h" + +class DigitallyImportedServiceBase; + + +class DigitallyImportedUrlHandler : public UrlHandler { +public: + DigitallyImportedUrlHandler(DigitallyImportedServiceBase* service); + + QString scheme() const; + UrlHandler_LoadResult StartLoading(const QUrl& url); + + void CancelTask(); + void LoadPlaylistFinished(QIODevice* device); + +private: + DigitallyImportedServiceBase* service_; + int task_id_; + + QUrl last_original_url_; +}; + +#endif // DIGITALLYIMPORTEDURLHANDLER_H diff --git a/src/internet/internetmodel.cpp b/src/internet/internetmodel.cpp index ca1c0924f..c13f2eeed 100644 --- a/src/internet/internetmodel.cpp +++ b/src/internet/internetmodel.cpp @@ -15,6 +15,7 @@ along with Clementine. If not, see . */ +#include "digitallyimportedservice.h" #include "icecastservice.h" #include "jamendoservice.h" #include "magnatuneservice.h" @@ -22,6 +23,7 @@ #include "internetmodel.h" #include "internetservice.h" #include "savedradio.h" +#include "skyfmservice.h" #include "somafmservice.h" #include "core/logging.h" #include "core/mergedproxymodel.h" @@ -54,17 +56,19 @@ InternetModel::InternetModel(BackgroundThread* db_thread, merged_model_->setSourceModel(this); + AddService(new DigitallyImportedService(this)); + AddService(new IcecastService(this)); + AddService(new JamendoService(this)); #ifdef HAVE_LIBLASTFM AddService(new LastFMService(this)); #endif -#ifdef HAVE_SPOTIFY - AddService(new SpotifyService(task_manager, this)); -#endif - AddService(new SomaFMService(this)); AddService(new MagnatuneService(this)); - AddService(new JamendoService(this)); - AddService(new IcecastService(this)); AddService(new SavedRadio(this)); + AddService(new SkyFmService(this)); + AddService(new SomaFMService(this)); +#ifdef HAVE_SPOTIFY + AddService(new SpotifyService(this)); +#endif } void InternetModel::AddService(InternetService *service) { @@ -86,6 +90,8 @@ void InternetModel::AddService(InternetService *service) { connect(service, SIGNAL(OpenSettingsAtPage(SettingsDialog::Page)), SIGNAL(OpenSettingsAtPage(SettingsDialog::Page))); connect(service, SIGNAL(AddToPlaylistSignal(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*))); connect(service, SIGNAL(destroyed()), SLOT(ServiceDeleted())); + + service->ReloadSettings(); } void InternetModel::RemoveService(InternetService* service) { diff --git a/src/internet/lastfmservice.cpp b/src/internet/lastfmservice.cpp index d5826c5a1..9e8a43a48 100644 --- a/src/internet/lastfmservice.cpp +++ b/src/internet/lastfmservice.cpp @@ -84,7 +84,6 @@ LastFMService::LastFMService(InternetModel* parent) neighbours_list_(NULL), connection_problems_(false) { - ReloadSettings(); //we emit the signal the first time to be sure the buttons are in the right state emit ScrobblingEnabledChanged(scrobbling_enabled_); diff --git a/src/internet/magnatuneservice.cpp b/src/internet/magnatuneservice.cpp index 80a527a7d..8a60931a2 100644 --- a/src/internet/magnatuneservice.cpp +++ b/src/internet/magnatuneservice.cpp @@ -76,8 +76,6 @@ MagnatuneService::MagnatuneService(InternetModel* parent) total_song_count_(0), network_(new NetworkAccessManager(this)) { - ReloadSettings(); - // Create the library backend in the database thread library_backend_ = new LibraryBackend; library_backend_->moveToThread(parent->db_thread()); diff --git a/src/internet/skyfmservice.cpp b/src/internet/skyfmservice.cpp new file mode 100644 index 000000000..f7fdaae56 --- /dev/null +++ b/src/internet/skyfmservice.cpp @@ -0,0 +1,91 @@ +/* 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 "digitallyimportedurlhandler.h" +#include "internetmodel.h" +#include "skyfmservice.h" +#include "core/taskmanager.h" + +#include +#include + +SkyFmService::SkyFmService(InternetModel* model, QObject* parent) + : DigitallyImportedServiceBase( + "SKY.fm", "SKY.fm", QUrl("http://www.sky.fm"), "sky.fm", + QUrl("http://listen.sky.fm"), "skyfm", QIcon(":/providers/skyfm.png"), + model, parent) +{ + playlists_ = QList() + << Playlist(false, "http://listen.sky.fm/public3/%1.pls") + << Playlist(true, "http://listen.sky.fm/premium_high/%1.pls?hash=%2") + << Playlist(false, "http://listen.sky.fm/public1/%1.pls") + << Playlist(true, "http://listen.sky.fm/premium_medium/%1.pls?hash=%2") + << Playlist(true, "http://listen.sky.fm/premium/%1.pls?hash=%2") + << Playlist(false, "http://listen.sky.fm/public5/%1.asx") + << Playlist(true, "http://listen.sky.fm/premium_wma_low/%1.asx?hash=%2") + << Playlist(true, "http://listen.sky.fm/premium_wma/%1.asx?hash=%2"); +} + +void SkyFmService::LoadStation(const QString& key) { + if (!is_premium_stream_selected()) { + // Non-premium streams can just start loading straight away + LoadPlaylist(key); + return; + } + + // Otherwise we have to get the user's hashKey + QNetworkRequest req(QUrl("http://www.sky.fm/configure_player.php")); + QByteArray postdata = "amember_login=" + QUrl::toPercentEncoding(username_) + + "&amember_pass=" + QUrl::toPercentEncoding(password_); + + QNetworkReply* reply = network_->post(req, postdata); + connect(reply, SIGNAL(finished()), SLOT(LoadHashKeyFinished())); + + last_key_ = key; +} + +void SkyFmService::LoadHashKeyFinished() { + QNetworkReply* reply = qobject_cast(sender()); + if (!reply) { + return; + } + + const QString page_data = QString::fromUtf8(reply->readAll().data()); + QRegExp re("hashKey\\s*=\\s*'([0-9a-f]+)'"); + + if (re.indexIn(page_data) == -1) { + url_handler_->CancelTask(); + emit StreamError(tr("Invalid SKY.fm username or password")); + return; + } + + LoadPlaylist(last_key_, re.cap(1)); +} + +void SkyFmService::LoadPlaylist(const QString& key, const QString& hash_key) { + QString url_template = playlists_[audio_type_].url_template_; + QUrl url; + + if (hash_key.isEmpty()) { + url = QUrl(url_template.arg(key)); + } else { + url = QUrl(url_template.arg(key, hash_key)); + } + + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished())); +} diff --git a/src/internet/skyfmservice.h b/src/internet/skyfmservice.h new file mode 100644 index 000000000..034b21e5d --- /dev/null +++ b/src/internet/skyfmservice.h @@ -0,0 +1,41 @@ +/* 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 SKYFMSERVICE_H +#define SKYFMSERVICE_H + +#include "digitallyimportedservicebase.h" + +class SkyFmService : public DigitallyImportedServiceBase { + Q_OBJECT + +public: + SkyFmService(InternetModel* model, QObject* parent = NULL); + + void LoadStation(const QString& key); + +private: + void LoadPlaylist(const QString& key, const QString& hash_key = QString()); + +private slots: + void LoadHashKeyFinished(); + +private: + QString last_key_; +}; + +#endif // SKYFMSERVICE_H diff --git a/src/internet/spotifyservice.cpp b/src/internet/spotifyservice.cpp index 6356d9414..3af3ef0fb 100644 --- a/src/internet/spotifyservice.cpp +++ b/src/internet/spotifyservice.cpp @@ -34,7 +34,7 @@ const char* SpotifyService::kSettingsGroup = "Spotify"; const char* SpotifyService::kBlobDownloadUrl = "http://spotify.clementine-player.org/"; const int SpotifyService::kSearchDelayMsec = 400; -SpotifyService::SpotifyService(TaskManager* task_manager, InternetModel* parent) +SpotifyService::SpotifyService(InternetModel* parent) : InternetService(kServiceName, parent, parent), server_(NULL), url_handler_(new SpotifyUrlHandler(this, this)), @@ -46,8 +46,7 @@ SpotifyService::SpotifyService(TaskManager* task_manager, InternetModel* parent) login_task_id_(0), pending_search_playlist_(NULL), context_menu_(NULL), - search_delay_(new QTimer(this)), - task_manager_(task_manager) { + search_delay_(new QTimer(this)) { // 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. @@ -441,16 +440,16 @@ void SpotifyService::SyncPlaylist() { int index = item->data(Role_UserPlaylistIndex).toInt(); server_->SyncUserPlaylist(index); playlist_sync_ids_[index] = - task_manager_->StartTask(tr("Syncing Spotify playlist")); + model()->task_manager()->StartTask(tr("Syncing Spotify playlist")); break; } case Type_InboxPlaylist: server_->SyncInbox(); - inbox_sync_id_ = task_manager_->StartTask(tr("Syncing Spotify inbox")); + inbox_sync_id_ = model()->task_manager()->StartTask(tr("Syncing Spotify inbox")); break; case Type_StarredPlaylist: server_->SyncStarred(); - starred_sync_id_ = task_manager_->StartTask(tr("Syncing Spotify starred tracks")); + starred_sync_id_ = model()->task_manager()->StartTask(tr("Syncing Spotify starred tracks")); break; default: break; @@ -582,9 +581,9 @@ void SpotifyService::SyncPlaylistProgress( qLog(Warning) << "Received sync progress for unknown playlist"; return; } - task_manager_->SetTaskProgress(task_id, progress.sync_progress(), 100); + model()->task_manager()->SetTaskProgress(task_id, progress.sync_progress(), 100); if (progress.sync_progress() == 100) { - task_manager_->SetTaskFinished(task_id); + model()->task_manager()->SetTaskFinished(task_id); if (progress.request().type() == protobuf::UserPlaylist) { playlist_sync_ids_.remove(task_id); } diff --git a/src/internet/spotifyservice.h b/src/internet/spotifyservice.h index c867b90da..d33bf0b5a 100644 --- a/src/internet/spotifyservice.h +++ b/src/internet/spotifyservice.h @@ -20,7 +20,7 @@ class SpotifyService : public InternetService { Q_OBJECT public: - SpotifyService(TaskManager* task_manager, InternetModel* parent); + SpotifyService(InternetModel* parent); ~SpotifyService(); enum Type { @@ -119,7 +119,6 @@ private: QTimer* search_delay_; - TaskManager* task_manager_; int inbox_sync_id_; int starred_sync_id_; QMap playlist_sync_ids_; diff --git a/src/ui/settingsdialog.cpp b/src/ui/settingsdialog.cpp index 4aaef2c77..47254b20c 100644 --- a/src/ui/settingsdialog.cpp +++ b/src/ui/settingsdialog.cpp @@ -468,6 +468,7 @@ void SettingsDialog::accept() { ui_->library_config->Save(); ui_->magnatune->Save(); + ui_->digitally_imported->Save(); ui_->global_shortcuts->Save(); streams_->SaveStreams(); @@ -558,6 +559,9 @@ void SettingsDialog::showEvent(QShowEvent*) { // Magnatune ui_->magnatune->Load(); + // Digitally Imported + ui_->digitally_imported->Load(); + // Global Shortcuts ui_->global_shortcuts->Load(); diff --git a/src/ui/settingsdialog.h b/src/ui/settingsdialog.h index 509e90982..570f08ce8 100644 --- a/src/ui/settingsdialog.h +++ b/src/ui/settingsdialog.h @@ -72,6 +72,7 @@ class SettingsDialog : public QDialog { Page_Spotify, #endif Page_Magnatune, + Page_DigitallyImported, Page_BackgroundStreams, Page_Proxy, Page_Transcoding, diff --git a/src/ui/settingsdialog.ui b/src/ui/settingsdialog.ui index fd33e16b8..687ba3639 100644 --- a/src/ui/settingsdialog.ui +++ b/src/ui/settingsdialog.ui @@ -93,6 +93,15 @@ :/providers/magnatune.png:/providers/magnatune.png + + + Digitally Imported + + + + :/providers/digitallyimported-32.png:/providers/digitallyimported-32.png + + Background Streams @@ -125,7 +134,7 @@ - 1 + 7 @@ -1095,6 +1104,19 @@ + + + + 0 + + + 0 + + + + + + @@ -1564,6 +1586,12 @@
transcoder/transcoderoptionswma.h
1 + + DigitallyImportedConfig + QWidget +
internet/digitallyimportedconfig.h
+ 1 +
list