From 63140f83cf99a64dd52a886f8b76fb39769fb97b Mon Sep 17 00:00:00 2001 From: David Sansome Date: Wed, 2 Nov 2011 23:41:46 +0000 Subject: [PATCH] Use an undocumented sky.fm/di.fm api to handle premium account logins, allowing us to remove dodgy code to scrape login information for each service individually --- src/CMakeLists.txt | 2 + src/internet/digitallyimportedclient.cpp | 85 +++++++++++++++ src/internet/digitallyimportedclient.h | 59 +++++++++++ src/internet/digitallyimportedservice.cpp | 40 +------ src/internet/digitallyimportedservice.h | 4 - src/internet/digitallyimportedservicebase.cpp | 100 +++++++++++------- src/internet/digitallyimportedservicebase.h | 57 +++++----- .../digitallyimportedsettingspage.cpp | 84 +++++++++++++-- src/internet/digitallyimportedsettingspage.h | 13 +++ src/internet/digitallyimportedsettingspage.ui | 76 ++++++++----- src/internet/digitallyimportedurlhandler.cpp | 10 -- src/internet/skyfmservice.cpp | 66 +----------- src/internet/skyfmservice.h | 13 --- 13 files changed, 385 insertions(+), 224 deletions(-) create mode 100644 src/internet/digitallyimportedclient.cpp create mode 100644 src/internet/digitallyimportedclient.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3762cb59b..e494c5bfd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,6 +132,7 @@ set(SOURCES globalsearch/tooltipactionwidget.cpp globalsearch/tooltipresultwidget.cpp + internet/digitallyimportedclient.cpp internet/digitallyimportedservice.cpp internet/digitallyimportedservicebase.cpp internet/digitallyimportedsettingspage.cpp @@ -381,6 +382,7 @@ set(HEADERS globalsearch/tooltipactionwidget.h globalsearch/tooltipresultwidget.h + internet/digitallyimportedclient.h internet/digitallyimportedservicebase.h internet/digitallyimportedsettingspage.h internet/groovesharksearchplaylisttype.h diff --git a/src/internet/digitallyimportedclient.cpp b/src/internet/digitallyimportedclient.cpp new file mode 100644 index 000000000..88bd5cf7e --- /dev/null +++ b/src/internet/digitallyimportedclient.cpp @@ -0,0 +1,85 @@ +/* This file is part of Clementine. + Copyright 2011, 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 "digitallyimportedclient.h" +#include "core/network.h" + +#include + +#include +#include + +// The API used here is undocumented - it was reverse engineered by watching +// calls made by the sky.fm android app: +// https://market.android.com/details?id=com.audioaddict.sky + +const char* DigitallyImportedClient::kApiUsername = "ephemeron"; +const char* DigitallyImportedClient::kApiPassword = "dayeiph0ne@pp"; + +const char* DigitallyImportedClient::kAuthUrl = + "http://api.audioaddict.com/%1/premium/auth"; + + +DigitallyImportedClient::DigitallyImportedClient(const QString& service_name, QObject* parent) + : QObject(parent), + network_(new NetworkAccessManager(this)), + service_name_(service_name) +{ +} + +QNetworkReply* DigitallyImportedClient::Auth(const QString& username, + const QString& password) { + QNetworkRequest req(QUrl(QString(kAuthUrl).arg(service_name_))); + req.setRawHeader("Authorization", + "Basic " + QString("%1:%2").arg(kApiUsername, kApiPassword) + .toAscii().toBase64()); + + QByteArray postdata = "username=" + QUrl::toPercentEncoding(username) + + "&password=" + QUrl::toPercentEncoding(password); + + return network_->post(req, postdata); +} + +DigitallyImportedClient::AuthReply +DigitallyImportedClient::ParseAuthReply(QNetworkReply* reply) const { + AuthReply ret; + ret.success_ = false; + ret.error_reason_ = tr("Unknown error"); + + QJson::Parser parser; + QVariantMap data = parser.parse(reply).toMap(); + + if (data.value("status", QString()).toString() != "OK" || + !data.contains("user")) { + ret.error_reason_ = data.value("reason", ret.error_reason_).toString(); + return ret; + } + + QVariantMap user = data["user"].toMap(); + if (!user.contains("first_name") || + !user.contains("last_name") || + !user.contains("expires") || + !user.contains("listen_hash")) + return ret; + + ret.success_ = true; + ret.first_name_ = user["first_name"].toString(); + ret.last_name_ = user["last_name"].toString(); + ret.expires_ = QDateTime::fromString(user["expires"].toString(), Qt::ISODate); + ret.listen_hash_ = user["listen_hash"].toString(); + return ret; +} diff --git a/src/internet/digitallyimportedclient.h b/src/internet/digitallyimportedclient.h new file mode 100644 index 000000000..4f7cc5e87 --- /dev/null +++ b/src/internet/digitallyimportedclient.h @@ -0,0 +1,59 @@ +/* This file is part of Clementine. + Copyright 2011, 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 DIGITALLYIMPORTEDCLIENT_H +#define DIGITALLYIMPORTEDCLIENT_H + +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; + +class DigitallyImportedClient : public QObject { + Q_OBJECT + +public: + DigitallyImportedClient(const QString& service_name, QObject* parent = 0); + + static const char* kApiUsername; + static const char* kApiPassword; + static const char* kAuthUrl; + + struct AuthReply { + bool success_; + + // Set if success_ == false + QString error_reason_; + + // Set if success_ == true + QString first_name_; + QString last_name_; + QDateTime expires_; + QString listen_hash_; + }; + + QNetworkReply* Auth(const QString& username, const QString& password); + AuthReply ParseAuthReply(QNetworkReply* reply) const; + +private: + QNetworkAccessManager* network_; + + QString service_name_; +}; + +#endif // DIGITALLYIMPORTEDCLIENT_H diff --git a/src/internet/digitallyimportedservice.cpp b/src/internet/digitallyimportedservice.cpp index f2e0802c4..5b25fe892 100644 --- a/src/internet/digitallyimportedservice.cpp +++ b/src/internet/digitallyimportedservice.cpp @@ -23,42 +23,10 @@ #include DigitallyImportedService::DigitallyImportedService(InternetModel* model, QObject* parent) - : DigitallyImportedServiceBase( - "DigitallyImported", "Digitally Imported", QUrl("http://www.di.fm"), - "di.fm", QUrl("http://listen.di.fm"), "digitallyimported", - ":/providers/digitallyimported.png", model, parent) + : DigitallyImportedServiceBase("DigitallyImported", 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"); + Init("Digitally Imported", QUrl("http://www.di.fm"), + "di.fm", QUrl("http://listen.di.fm"), "digitallyimported", + ":/providers/digitallyimported.png", "di"); } -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 index 6add8c5f7..b87e289e8 100644 --- a/src/internet/digitallyimportedservice.h +++ b/src/internet/digitallyimportedservice.h @@ -23,10 +23,6 @@ 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 index 09f55fc25..3a73f67b5 100644 --- a/src/internet/digitallyimportedservicebase.cpp +++ b/src/internet/digitallyimportedservicebase.cpp @@ -15,6 +15,7 @@ along with Clementine. If not, see . */ +#include "digitallyimportedclient.h" #include "digitallyimportedservicebase.h" #include "digitallyimportedurlhandler.h" #include "internetmodel.h" @@ -37,29 +38,48 @@ const int DigitallyImportedServiceBase::kStreamsCacheDurationSecs = 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 QString& icon_path, - InternetModel* model, QObject* parent) + const QString& name, InternetModel* model, QObject* parent) : InternetService(name, model, parent), network_(new NetworkAccessManager(this)), url_handler_(new DigitallyImportedUrlHandler(this)), - audio_type_(0), + basic_audio_type_(1), + premium_audio_type_(2), task_id_(-1), - homepage_url_(homepage_url), - homepage_name_(homepage_name), - stream_list_url_(stream_list_url), - icon_path_(icon_path), - icon_(icon_path), - service_description_(description), - url_scheme_(url_scheme), root_(NULL), context_menu_(NULL), - context_item_(NULL) + context_item_(NULL), + api_client_(NULL) { - model->player()->RegisterUrlHandler(url_handler_); + basic_playlists_ + << "http://listen.%1/public3/%2.pls" + << "http://listen.%1/public1/%2.pls" + << "http://listen.%1/public5/%2.asx"; - model->global_search()->AddProvider(new DigitallyImportedSearchProvider(this, this)); + premium_playlists_ + << "http://listen.%1/premium_high/%3.pls?hash=%3" + << "http://listen.%1/premium_medium/%2.pls?hash=%3" + << "http://listen.%1/premium/%2.pls?hash=%3" + << "http://listen.%1/premium_wma_low/%2.asx?hash=%3" + << "http://listen.%1/premium_wma/%2.asx?hash=%3"; +} + +void DigitallyImportedServiceBase::Init( + const QString& description, const QUrl& homepage_url, + const QString& homepage_name, const QUrl& stream_list_url, + const QString& url_scheme, const QString& icon_path, + const QString& api_service_name) { + service_description_ = description; + homepage_url_ = homepage_url; + homepage_name_ = homepage_name; + stream_list_url_ = stream_list_url; + url_scheme_ = url_scheme; + icon_path_ = icon_path; + api_service_name_ = api_service_name; + + model()->player()->RegisterUrlHandler(url_handler_); + model()->global_search()->AddProvider(new DigitallyImportedSearchProvider(this, this)); + + api_client_ = new DigitallyImportedClient(api_service_name, this); } DigitallyImportedServiceBase::~DigitallyImportedServiceBase() { @@ -176,9 +196,10 @@ void DigitallyImportedServiceBase::ReloadSettings() { QSettings s; s.beginGroup(kSettingsGroup); - audio_type_ = s.value("audio_type", 0).toInt(); + basic_audio_type_ = s.value("basic_audio_type", 1).toInt(); + premium_audio_type_ = s.value("premium_audio_type", 2).toInt(); username_ = s.value("username").toString(); - password_ = s.value("password").toString(); + listen_hash_ = s.value("listen_hash").toString(); last_refreshed_streams_ = s.value("last_refreshed_" + url_scheme_).toDateTime(); saved_streams_ = LoadStreams(); } @@ -204,24 +225,8 @@ void DigitallyImportedServiceBase::ShowContextMenu( 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_; + return !listen_hash_.isEmpty(); } void DigitallyImportedServiceBase::LoadPlaylistFinished() { @@ -234,11 +239,7 @@ void DigitallyImportedServiceBase::LoadPlaylistFinished() { 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")); - } + emit StreamError(tr("Error loading di.fm playlist")); } else { url_handler_->LoadPlaylistFinished(reply); } @@ -302,3 +303,24 @@ DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::Streams() return saved_streams_; } + +void DigitallyImportedServiceBase::LoadStation(const QString& key) { + QUrl playlist_url; + + if (is_premium_account()) { + playlist_url = QUrl(premium_playlists_[premium_audio_type_].arg( + homepage_name_, key, listen_hash_)); + } else { + playlist_url = QUrl(basic_playlists_[basic_audio_type_].arg( + homepage_name_, key)); + } + + qLog(Debug) << "Getting playlist URL" << playlist_url; + + QNetworkReply* reply = network_->get(QNetworkRequest(playlist_url)); + connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished())); +} + +QModelIndex DigitallyImportedServiceBase::GetCurrentIndex() { + return context_item_->index(); +} diff --git a/src/internet/digitallyimportedservicebase.h b/src/internet/digitallyimportedservicebase.h index 628813091..daf7f9d2e 100644 --- a/src/internet/digitallyimportedservicebase.h +++ b/src/internet/digitallyimportedservicebase.h @@ -20,6 +20,7 @@ #include "internetservice.h" +class DigitallyImportedClient; class DigitallyImportedUrlHandler; class QNetworkAccessManager; @@ -30,11 +31,8 @@ class DigitallyImportedServiceBase : public InternetService { 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 QString& icon_path, - InternetModel* model, QObject* parent = NULL); + DigitallyImportedServiceBase(const QString& name, InternetModel* model, + QObject* parent = NULL); ~DigitallyImportedServiceBase(); static const char* kSettingsGroup; @@ -46,8 +44,6 @@ public: void ReloadSettings(); - bool is_valid_stream_selected() const; - bool is_premium_stream_selected() const; bool is_premium_account() const; const QUrl& homepage_url() const { return homepage_url_; } @@ -57,6 +53,7 @@ public: const QIcon& icon() const { return icon_; } const QString& service_description() const { return service_description_; } const QString& url_scheme() const { return url_scheme_; } + const QString& api_service_name() const { return api_service_name_; } // Public for the global search provider. struct Stream { @@ -76,20 +73,14 @@ signals: void StreamsChanged(); protected: - struct Playlist { - Playlist(bool premium, const QString& url_template) - : premium_(premium), url_template_(url_template) {} - - bool premium_; - QString url_template_; - }; + // Subclasses must call this from their constructor + void Init(const QString& description, const QUrl& homepage_url, + const QString& homepage_name, const QUrl& stream_list_url, + const QString& url_scheme, const QString& icon_path, + const QString& api_service_name); QModelIndex GetCurrentIndex(); - // Called by DigitallyImportedUrlHandler, implemented by subclasses, must - // call LoadPlaylistFinished eventually. - virtual void LoadStation(const QString& key) = 0; - protected slots: void LoadPlaylistFinished(); @@ -100,23 +91,13 @@ private slots: void RefreshStreamsFinished(); void ShowSettingsDialog(); -protected: - QNetworkAccessManager* network_; - DigitallyImportedUrlHandler* url_handler_; - - int audio_type_; - QString username_; - QString password_; - - int task_id_; - - QList playlists_; - private: void PopulateStreams(); StreamList LoadStreams() const; void SaveStreams(const StreamList& streams); + void LoadStation(const QString& key); + private: // Set by subclasses through the constructor QUrl homepage_url_; @@ -126,6 +107,20 @@ private: QIcon icon_; QString service_description_; QString url_scheme_; + QString api_service_name_; + + QStringList basic_playlists_; + QStringList premium_playlists_; + + QNetworkAccessManager* network_; + DigitallyImportedUrlHandler* url_handler_; + + int basic_audio_type_; + int premium_audio_type_; + QString username_; + QString listen_hash_; + + int task_id_; QStandardItem* root_; @@ -134,6 +129,8 @@ private: QList saved_streams_; QDateTime last_refreshed_streams_; + + DigitallyImportedClient* api_client_; }; #endif // DIGITALLYIMPORTEDSERVICEBASE_H diff --git a/src/internet/digitallyimportedsettingspage.cpp b/src/internet/digitallyimportedsettingspage.cpp index 2785772b3..c41f7ebd4 100644 --- a/src/internet/digitallyimportedsettingspage.cpp +++ b/src/internet/digitallyimportedsettingspage.cpp @@ -15,47 +15,119 @@ along with Clementine. If not, see . */ +#include "digitallyimportedclient.h" #include "digitallyimportedservicebase.h" #include "digitallyimportedsettingspage.h" #include "ui_digitallyimportedsettingspage.h" +#include "core/closure.h" +#include +#include #include DigitallyImportedSettingsPage::DigitallyImportedSettingsPage(SettingsDialog* dialog) : SettingsPage(dialog), - ui_(new Ui_DigitallyImportedSettingsPage) + ui_(new Ui_DigitallyImportedSettingsPage), + client_(new DigitallyImportedClient("di", this)) { ui_->setupUi(this); setWindowIcon(QIcon(":/providers/digitallyimported-32.png")); + connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout())); + connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login())); + connect(ui_->login, SIGNAL(clicked()), SLOT(Login())); + + ui_->login_state->AddCredentialField(ui_->username); + ui_->login_state->AddCredentialField(ui_->password); + ui_->login_state->AddCredentialGroup(ui_->credential_group); + ui_->login_state->SetAccountTypeText(tr( "You can listen for free without an account, but Premium members can " "listen to higher quality streams without advertisements.")); ui_->login_state->SetAccountTypeVisible(true); - ui_->login_state->HideLoggedInState(); } DigitallyImportedSettingsPage::~DigitallyImportedSettingsPage() { delete ui_; } +void DigitallyImportedSettingsPage::Login() { + ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress); + + QNetworkReply* reply = client_->Auth(ui_->username->text(), ui_->password->text()); + NewClosure(reply, SIGNAL(finished()), + this, SLOT(LoginFinished(QNetworkReply*)), reply); +} + +void DigitallyImportedSettingsPage::LoginFinished(QNetworkReply* reply) { + DigitallyImportedClient::AuthReply result = client_->ParseAuthReply(reply); + + QString name = QString("%1 %2").arg(result.first_name_, result.last_name_); + UpdateLoginState(result.listen_hash_, name, result.expires_); + + if (result.success_) { + // Clear the password field just to be sure + ui_->password->clear(); + + // Save the listen key and account information + QSettings s; + s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup); + s.setValue("listen_hash", result.listen_hash_); + s.setValue("full_name", name); + s.setValue("expires", result.expires_); + } else { + QMessageBox::warning(this, tr("Authentication failed"), + result.error_reason_); + } +} + +void DigitallyImportedSettingsPage::UpdateLoginState( + const QString& listen_hash, const QString& name, const QDateTime& expires) { + if (listen_hash.isEmpty()) { + ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut); + ui_->login_state->SetAccountTypeVisible(true); + } else { + ui_->login_state->SetLoggedIn( + LoginStateWidget::LoggedIn, + name + " (" + tr("Expires on %1").arg(expires.date().toString(Qt::SystemLocaleLongDate)) + ")"); + ui_->login_state->SetAccountTypeVisible(false); + } + + ui_->premium_audio_type->setEnabled(!listen_hash.isEmpty()); + ui_->premium_audio_label->setEnabled(!listen_hash.isEmpty()); +} + +void DigitallyImportedSettingsPage::Logout() { + QSettings s; + s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup); + s.setValue("listen_hash", QString()); + s.setValue("full_name", QString()); + s.setValue("expires", QDateTime()); + + UpdateLoginState(QString(), QString(), QDateTime()); +} + void DigitallyImportedSettingsPage::Load() { QSettings s; s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup); - ui_->audio_type->setCurrentIndex(s.value("audio_type", 0).toInt()); + ui_->basic_audio_type->setCurrentIndex(s.value("basic_audio_type", 1).toInt()); + ui_->premium_audio_type->setCurrentIndex(s.value("premium_audio_type", 2).toInt()); ui_->username->setText(s.value("username").toString()); - ui_->password->setText(s.value("password").toString()); + + UpdateLoginState(s.value("listen_hash").toString(), + s.value("full_name").toString(), + s.value("expires").toDateTime()); } void DigitallyImportedSettingsPage::Save() { QSettings s; s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup); - s.setValue("audio_type", ui_->audio_type->currentIndex()); + s.setValue("basic_audio_type", ui_->basic_audio_type->currentIndex()); + s.setValue("premium_audio_type", ui_->premium_audio_type->currentIndex()); s.setValue("username", ui_->username->text()); - s.setValue("password", ui_->password->text()); } diff --git a/src/internet/digitallyimportedsettingspage.h b/src/internet/digitallyimportedsettingspage.h index e6899f2bb..bb58729e0 100644 --- a/src/internet/digitallyimportedsettingspage.h +++ b/src/internet/digitallyimportedsettingspage.h @@ -20,6 +20,7 @@ #include "ui/settingspage.h" +class DigitallyImportedClient; class Ui_DigitallyImportedSettingsPage; class DigitallyImportedSettingsPage : public SettingsPage { @@ -32,8 +33,20 @@ public: void Load(); void Save(); +private slots: + void Login(); + void Logout(); + + void LoginFinished(QNetworkReply* reply); + +private: + void UpdateLoginState(const QString& listen_hash, const QString& name, + const QDateTime& expires); + private: Ui_DigitallyImportedSettingsPage* ui_; + + DigitallyImportedClient* client_; }; #endif // DIGITALLYIMPORTEDSETTINGSPAGE_H diff --git a/src/internet/digitallyimportedsettingspage.ui b/src/internet/digitallyimportedsettingspage.ui index 9cbfe1494..9eb5a6094 100644 --- a/src/internet/digitallyimportedsettingspage.ui +++ b/src/internet/digitallyimportedsettingspage.ui @@ -18,7 +18,7 @@ - + 0 @@ -28,7 +28,7 @@ Account details (Premium) - + @@ -37,7 +37,18 @@ - + + + + + + + + Login + + + + @@ -53,7 +64,7 @@ - + <a href="http://www.di.fm/premium/">Upgrade to Premium now</a> @@ -82,52 +93,63 @@ - + - Audio type + Basic audio type - + MP3 96k - - - MP3 256k (Premium only) - - AAC 32k - - - AAC 64k (Premium only) - - - - - AAC 128k (Premium only) - - Windows Media 40k + + + + + + Premium audio type + + + + + - Windows Media 64k (Premium only) + MP3 256k - Windows Media 128k (Premium only) + AAC 64k + + + + + AAC 128k + + + + + Windows Media 64k + + + + + Windows Media 128k @@ -158,6 +180,12 @@ 1 + + username + password + login + basic_audio_type + diff --git a/src/internet/digitallyimportedurlhandler.cpp b/src/internet/digitallyimportedurlhandler.cpp index c107d10b2..e5e55ba39 100644 --- a/src/internet/digitallyimportedurlhandler.cpp +++ b/src/internet/digitallyimportedurlhandler.cpp @@ -39,16 +39,6 @@ UrlHandler::LoadResult DigitallyImportedUrlHandler::StartLoading(const QUrl& url 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; diff --git a/src/internet/skyfmservice.cpp b/src/internet/skyfmservice.cpp index fb6bfa385..6f05734cb 100644 --- a/src/internet/skyfmservice.cpp +++ b/src/internet/skyfmservice.cpp @@ -18,6 +18,7 @@ #include "digitallyimportedurlhandler.h" #include "internetmodel.h" #include "skyfmservice.h" +#include "core/logging.h" #include "core/taskmanager.h" #include @@ -25,67 +26,8 @@ SkyFmService::SkyFmService(InternetModel* model, QObject* parent) : DigitallyImportedServiceBase( - "SKY.fm", "SKY.fm", QUrl("http://www.sky.fm"), "sky.fm", - QUrl("http://listen.sky.fm"), "skyfm", ":/providers/skyfm.png", - model, parent) + "SKY.fm", 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())); + Init("SKY.fm", QUrl("http://www.sky.fm"), "sky.fm", + QUrl("http://listen.sky.fm"), "skyfm", ":/providers/skyfm.png", "sky"); } diff --git a/src/internet/skyfmservice.h b/src/internet/skyfmservice.h index 034b21e5d..573f8b9d3 100644 --- a/src/internet/skyfmservice.h +++ b/src/internet/skyfmservice.h @@ -21,21 +21,8 @@ #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