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 000000000..bfef73f5c Binary files /dev/null and b/data/providers/digitallyimported-32.png differ diff --git a/data/providers/digitallyimported.png b/data/providers/digitallyimported.png new file mode 100644 index 000000000..7e5840876 Binary files /dev/null and b/data/providers/digitallyimported.png differ diff --git a/data/providers/skyfm.png b/data/providers/skyfm.png new file mode 100644 index 000000000..e96adaa98 Binary files /dev/null and b/data/providers/skyfm.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 054fe5288..0ed1d8ec7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,6 +118,10 @@ set(SOURCES engines/gstenginepipeline.cpp engines/gstelementdeleter.cpp + internet/digitallyimportedconfig.cpp + internet/digitallyimportedservice.cpp + internet/digitallyimportedservicebase.cpp + internet/digitallyimportedurlhandler.cpp internet/icecastbackend.cpp internet/icecastfilterwidget.cpp internet/icecastmodel.cpp @@ -136,6 +140,7 @@ set(SOURCES internet/magnatuneservice.cpp internet/magnatuneurlhandler.cpp internet/savedradio.cpp + internet/skyfmservice.cpp internet/somafmservice.cpp internet/somafmurlhandler.cpp @@ -346,6 +351,8 @@ set(HEADERS engines/gstenginepipeline.h engines/gstelementdeleter.h + internet/digitallyimportedconfig.h + internet/digitallyimportedservicebase.h internet/icecastbackend.h internet/icecastfilterwidget.h internet/icecastmodel.h @@ -361,6 +368,7 @@ set(HEADERS internet/magnatunedownloaddialog.h internet/magnatuneservice.h internet/savedradio.h + internet/skyfmservice.h internet/somafmservice.h internet/somafmurlhandler.h @@ -502,6 +510,7 @@ set(UI devices/deviceproperties.ui + internet/digitallyimportedconfig.ui internet/icecastfilterwidget.ui internet/magnatuneconfig.ui internet/magnatunedownloaddialog.ui diff --git a/src/internet/digitallyimportedconfig.cpp b/src/internet/digitallyimportedconfig.cpp new file mode 100644 index 000000000..7265a3c49 --- /dev/null +++ b/src/internet/digitallyimportedconfig.cpp @@ -0,0 +1,54 @@ +/* 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 "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