diff --git a/data/data.qrc b/data/data.qrc
index 47ebe1680..764267795 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -341,5 +341,6 @@
providers/podcast16.png
providers/podcast32.png
providers/mygpo32.png
+ providers/itunes.png
diff --git a/data/providers/itunes.png b/data/providers/itunes.png
new file mode 100644
index 000000000..656b12ed7
Binary files /dev/null and b/data/providers/itunes.png differ
diff --git a/data/schema/schema-37.sql b/data/schema/schema-37.sql
index c83995408..0bb53957c 100644
--- a/data/schema/schema-37.sql
+++ b/data/schema/schema-37.sql
@@ -4,7 +4,8 @@ CREATE TABLE podcasts (
description TEXT,
copyright TEXT,
link TEXT,
- image_url TEXT,
+ image_url_large TEXT,
+ image_url_small TEXT,
author TEXT,
owner_name TEXT,
owner_email TEXT,
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fe926ed99..a2770279d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -234,6 +234,7 @@ set(SOURCES
podcasts/gpoddersearchpage.cpp
podcasts/gpoddertoptagsmodel.cpp
podcasts/gpoddertoptagspage.cpp
+ podcasts/itunessearchpage.cpp
podcasts/podcast.cpp
podcasts/podcastbackend.cpp
podcasts/podcastdiscoverymodel.cpp
@@ -485,6 +486,7 @@ set(HEADERS
podcasts/gpoddersearchpage.h
podcasts/gpoddertoptagsmodel.h
podcasts/gpoddertoptagspage.h
+ podcasts/itunessearchpage.h
podcasts/podcastbackend.h
podcasts/podcastdiscoverymodel.h
podcasts/podcastinfowidget.h
@@ -612,6 +614,7 @@ set(UI
podcasts/addpodcastbyurl.ui
podcasts/addpodcastdialog.ui
podcasts/gpoddersearchpage.ui
+ podcasts/itunessearchpage.ui
podcasts/podcastinfowidget.ui
remote/remotesettingspage.ui
diff --git a/src/podcasts/addpodcastdialog.cpp b/src/podcasts/addpodcastdialog.cpp
index 62d1f1ace..c9029a730 100644
--- a/src/podcasts/addpodcastdialog.cpp
+++ b/src/podcasts/addpodcastdialog.cpp
@@ -19,6 +19,7 @@
#include "addpodcastbyurl.h"
#include "gpoddersearchpage.h"
#include "gpoddertoptagspage.h"
+#include "itunessearchpage.h"
#include "podcastbackend.h"
#include "podcastdiscoverymodel.h"
#include "ui_addpodcastdialog.h"
@@ -58,6 +59,7 @@ AddPodcastDialog::AddPodcastDialog(Application* app, QWidget* parent)
AddPage(new AddPodcastByUrl(app, this));
AddPage(new GPodderTopTagsPage(app, this));
AddPage(new GPodderSearchPage(app, this));
+ AddPage(new ITunesSearchPage(app, this));
ui_->provider_list->setCurrentRow(0);
}
diff --git a/src/podcasts/itunessearchpage.cpp b/src/podcasts/itunessearchpage.cpp
new file mode 100644
index 000000000..4df8a64c8
--- /dev/null
+++ b/src/podcasts/itunessearchpage.cpp
@@ -0,0 +1,103 @@
+/* This file is part of Clementine.
+ Copyright 2012, 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 "itunessearchpage.h"
+#include "podcast.h"
+#include "podcastdiscoverymodel.h"
+#include "ui_itunessearchpage.h"
+#include "core/closure.h"
+#include "core/network.h"
+
+#include
+
+#include
+#include
+
+const char* ITunesSearchPage::kUrlBase =
+ "http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStoreServices.woa/wa/wsSearch?country=US&media=podcast";
+
+ITunesSearchPage::ITunesSearchPage(Application* app, QWidget* parent)
+ : AddPodcastPage(app, parent),
+ ui_(new Ui_ITunesSearchPage),
+ network_(new NetworkAccessManager(this))
+{
+ ui_->setupUi(this);
+ connect(ui_->search, SIGNAL(clicked()), SLOT(SearchClicked()));
+}
+
+ITunesSearchPage::~ITunesSearchPage() {
+ delete ui_;
+}
+
+void ITunesSearchPage::SearchClicked() {
+ emit Busy(true);
+
+ QUrl url(QUrl::fromEncoded(kUrlBase));
+ url.addQueryItem("term", ui_->query->text());
+
+ QNetworkReply* reply = network_->get(QNetworkRequest(url));
+ NewClosure(reply, SIGNAL(finished()),
+ this, SLOT(SearchFinished(QNetworkReply*)),
+ reply);
+}
+
+void ITunesSearchPage::SearchFinished(QNetworkReply* reply) {
+ reply->deleteLater();
+ emit Busy(false);
+
+ model()->clear();
+
+ // Was there a network error?
+ if (reply->error() != QNetworkReply::NoError) {
+ QMessageBox::warning(this, tr("Failed to fetch podcasts"), reply->errorString());
+ return;
+ }
+
+ QJson::Parser parser;
+ QVariant data = parser.parse(reply);
+
+ // Was it valid JSON?
+ if (data.isNull()) {
+ QMessageBox::warning(this, tr("Failed to fetch podcasts"),
+ tr("There was a problem parsing the response from the iTunes Store"));
+ return;
+ }
+
+ // Was there an error message in the JSON?
+ if (data.toMap().contains("errorMessage")) {
+ QMessageBox::warning(this, tr("Failed to fetch podcasts"),
+ data.toMap()["errorMessage"].toString());
+ return;
+ }
+
+ foreach (const QVariant& result_variant, data.toMap()["results"].toList()) {
+ QVariantMap result(result_variant.toMap());
+ if (result["kind"].toString() != "podcast") {
+ continue;
+ }
+
+ Podcast podcast;
+ podcast.set_author(result["artistName"].toString());
+ podcast.set_title(result["trackName"].toString());
+ podcast.set_url(result["feedUrl"].toUrl());
+ podcast.set_link(result["trackViewUrl"].toUrl());
+ podcast.set_image_url_small(result["artworkUrl30"].toString());
+ podcast.set_image_url_large(result["artworkUrl100"].toString());
+
+ model()->appendRow(model()->CreatePodcastItem(podcast));
+ }
+}
diff --git a/src/podcasts/itunessearchpage.h b/src/podcasts/itunessearchpage.h
new file mode 100644
index 000000000..c976e0b4a
--- /dev/null
+++ b/src/podcasts/itunessearchpage.h
@@ -0,0 +1,47 @@
+/* This file is part of Clementine.
+ Copyright 2012, 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 ITUNESSEARCHPAGE_H
+#define ITUNESSEARCHPAGE_H
+
+#include "addpodcastpage.h"
+
+class Ui_ITunesSearchPage;
+
+class QNetworkAccessManager;
+class QNetworkReply;
+
+class ITunesSearchPage : public AddPodcastPage {
+ Q_OBJECT
+
+public:
+ ITunesSearchPage(Application* app, QWidget* parent);
+ ~ITunesSearchPage();
+
+ static const char* kUrlBase;
+
+private slots:
+ void SearchClicked();
+ void SearchFinished(QNetworkReply* reply);
+
+private:
+ Ui_ITunesSearchPage* ui_;
+
+ QNetworkAccessManager* network_;
+};
+
+#endif // ITUNESSEARCHPAGE_H
diff --git a/src/podcasts/itunessearchpage.ui b/src/podcasts/itunessearchpage.ui
new file mode 100644
index 000000000..ed82bb339
--- /dev/null
+++ b/src/podcasts/itunessearchpage.ui
@@ -0,0 +1,51 @@
+
+
+ ITunesSearchPage
+
+
+
+ 0
+ 0
+ 516
+ 69
+
+
+
+ Search iTunes
+
+
+
+ :/providers/itunes.png:/providers/itunes.png
+
+
+
+ 0
+
+ -
+
+
+ Enter search terms below to find podcasts in the iTunes Store
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ Search
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/podcasts/podcast.cpp b/src/podcasts/podcast.cpp
index f5971b86d..a095cf353 100644
--- a/src/podcasts/podcast.cpp
+++ b/src/podcasts/podcast.cpp
@@ -24,7 +24,8 @@
const QStringList Podcast::kColumns = QStringList()
<< "url" << "title" << "description" << "copyright" << "link"
- << "image_url" << "author" << "owner_name" << "owner_email" << "extra";
+ << "image_url_large" << "image_url_small" << "author" << "owner_name"
+ << "owner_email" << "extra";
const QString Podcast::kColumnSpec = Podcast::kColumns.join(", ");
const QString Podcast::kJoinSpec = Utilities::Prepend("p.", Podcast::kColumns).join(", ");
@@ -42,7 +43,8 @@ struct Podcast::Private : public QSharedData {
QString description_;
QString copyright_;
QUrl link_;
- QUrl image_url_;
+ QUrl image_url_large_;
+ QUrl image_url_small_;
// iTunes extensions
QString author_;
@@ -85,7 +87,8 @@ const QString& Podcast::title() const { return d->title_; }
const QString& Podcast::description() const { return d->description_; }
const QString& Podcast::copyright() const { return d->copyright_; }
const QUrl& Podcast::link() const { return d->link_; }
-const QUrl& Podcast::image_url() const { return d->image_url_; }
+const QUrl& Podcast::image_url_large() const { return d->image_url_large_; }
+const QUrl& Podcast::image_url_small() const { return d->image_url_small_; }
const QString& Podcast::author() const { return d->author_; }
const QString& Podcast::owner_name() const { return d->owner_name_; }
const QString& Podcast::owner_email() const { return d->owner_email_; }
@@ -98,7 +101,8 @@ void Podcast::set_title(const QString& v) { d->title_ = v; }
void Podcast::set_description(const QString& v) { d->description_ = v; }
void Podcast::set_copyright(const QString& v) { d->copyright_ = v; }
void Podcast::set_link(const QUrl& v) { d->link_ = v; }
-void Podcast::set_image_url(const QUrl& v) { d->image_url_ = v; }
+void Podcast::set_image_url_large(const QUrl& v) { d->image_url_large_ = v; }
+void Podcast::set_image_url_small(const QUrl& v) { d->image_url_small_ = v; }
void Podcast::set_author(const QString& v) { d->author_ = v; }
void Podcast::set_owner_name(const QString& v) { d->owner_name_ = v; }
void Podcast::set_owner_email(const QString& v) { d->owner_email_ = v; }
@@ -117,12 +121,13 @@ void Podcast::InitFromQuery(const QSqlQuery& query) {
d->description_ = query.value(3).toString();
d->copyright_ = query.value(4).toString();
d->link_ = QUrl::fromEncoded(query.value(5).toByteArray());
- d->image_url_ = QUrl::fromEncoded(query.value(6).toByteArray());
- d->author_ = query.value(7).toString();
- d->owner_name_ = query.value(8).toString();
- d->owner_email_ = query.value(9).toString();
+ d->image_url_large_ = QUrl::fromEncoded(query.value(6).toByteArray());
+ d->image_url_small_ = QUrl::fromEncoded(query.value(7).toByteArray());
+ d->author_ = query.value(8).toString();
+ d->owner_name_ = query.value(9).toString();
+ d->owner_email_ = query.value(10).toString();
- QDataStream extra_stream(query.value(10).toByteArray());
+ QDataStream extra_stream(query.value(11).toByteArray());
extra_stream >> d->extra_;
}
@@ -132,7 +137,8 @@ void Podcast::BindToQuery(QSqlQuery* query) const {
query->bindValue(":description", d->description_);
query->bindValue(":copyright", d->copyright_);
query->bindValue(":link", d->link_.toEncoded());
- query->bindValue(":image_url", d->image_url_.toEncoded());
+ query->bindValue(":image_url_large", d->image_url_large_.toEncoded());
+ query->bindValue(":image_url_small", d->image_url_small_.toEncoded());
query->bindValue(":author", d->author_);
query->bindValue(":owner_name", d->owner_name_);
query->bindValue(":owner_email", d->owner_email_);
@@ -149,7 +155,7 @@ void Podcast::InitFromGpo(const mygpo::Podcast* podcast) {
d->title_ = podcast->title();
d->description_ = podcast->description();
d->link_ = podcast->website();
- d->image_url_ = podcast->logoUrl();
+ d->image_url_large_ = podcast->logoUrl();
set_extra("gpodder:subscribers", podcast->subscribers());
set_extra("gpodder:subscribers_last_week", podcast->subscribersLastWeek());
diff --git a/src/podcasts/podcast.h b/src/podcasts/podcast.h
index 469772106..47a2f5f96 100644
--- a/src/podcasts/podcast.h
+++ b/src/podcasts/podcast.h
@@ -54,7 +54,8 @@ public:
const QString& description() const;
const QString& copyright() const;
const QUrl& link() const;
- const QUrl& image_url() const;
+ const QUrl& image_url_large() const;
+ const QUrl& image_url_small() const;
const QString& author() const;
const QString& owner_name() const;
const QString& owner_email() const;
@@ -67,13 +68,19 @@ public:
void set_description(const QString& v);
void set_copyright(const QString& v);
void set_link(const QUrl& v);
- void set_image_url(const QUrl& v);
+ void set_image_url_large(const QUrl& v);
+ void set_image_url_small(const QUrl& v);
void set_author(const QString& v);
void set_owner_name(const QString& v);
void set_owner_email(const QString& v);
void set_extra(const QVariantMap& v);
void set_extra(const QString& key, const QVariant& value);
+ // Small images are suitable for 16x16 icons in lists. Large images are
+ // used in detailed information displays.
+ const QUrl& ImageUrlLarge() const { return image_url_large().isValid() ? image_url_large() : image_url_small(); }
+ const QUrl& ImageUrlSmall() const { return image_url_small().isValid() ? image_url_small() : image_url_large(); }
+
// These are stored in a different database table, and aren't loaded or
// persisted by InitFromQuery or BindToQuery.
const PodcastEpisodeList& episodes() const;
diff --git a/src/podcasts/podcastdiscoverymodel.cpp b/src/podcasts/podcastdiscoverymodel.cpp
index e124272db..1012f0d07 100644
--- a/src/podcasts/podcastdiscoverymodel.cpp
+++ b/src/podcasts/podcastdiscoverymodel.cpp
@@ -72,8 +72,8 @@ void PodcastDiscoveryModel::LazyLoadImage(const QModelIndex& index) {
Podcast podcast = index.data(Role_Podcast).value();
- if (podcast.image_url().isValid()) {
- icon_loader_->LoadIcon(podcast.image_url().toString(), QString(), item);
+ if (podcast.ImageUrlSmall().isValid()) {
+ icon_loader_->LoadIcon(podcast.ImageUrlSmall().toString(), QString(), item);
}
}
diff --git a/src/podcasts/podcastinfowidget.cpp b/src/podcasts/podcastinfowidget.cpp
index 0a5287d01..2669aad14 100644
--- a/src/podcasts/podcastinfowidget.cpp
+++ b/src/podcasts/podcastinfowidget.cpp
@@ -78,10 +78,10 @@ void PodcastInfoWidget::SetPodcast(const Podcast& podcast) {
podcast_ = podcast;
- if (podcast.image_url().isValid()) {
+ if (podcast.ImageUrlLarge().isValid()) {
// Start loading an image for this item.
image_id_ = app_->album_cover_loader()->LoadImageAsync(
- cover_options_, podcast.image_url().toString(), QString());
+ cover_options_, podcast.ImageUrlLarge().toString(), QString());
}
ui_->image->hide();
diff --git a/src/podcasts/podcastparser.cpp b/src/podcasts/podcastparser.cpp
index 4ef51e80c..5d510714d 100644
--- a/src/podcasts/podcastparser.cpp
+++ b/src/podcasts/podcastparser.cpp
@@ -96,7 +96,7 @@ void PodcastParser::ParseImage(QXmlStreamReader* reader, Podcast* ret) const {
case QXmlStreamReader::StartElement: {
const QStringRef name = reader->name();
if (name == "url") {
- ret->set_image_url(QUrl(reader->readElementText()));
+ ret->set_image_url_large(QUrl(reader->readElementText()));
} else {
Utilities::ConsumeCurrentElement(reader);
}
diff --git a/src/podcasts/podcastservice.cpp b/src/podcasts/podcastservice.cpp
index 7e1f328d8..61a0e33e8 100644
--- a/src/podcasts/podcastservice.cpp
+++ b/src/podcasts/podcastservice.cpp
@@ -100,8 +100,8 @@ QStandardItem* PodcastService::CreatePodcastItem(const Podcast& podcast) {
item->setData(QVariant::fromValue(podcast), Role_Podcast);
// Load the podcast's image if it has one
- if (podcast.image_url().isValid()) {
- icon_loader_->LoadIcon(podcast.image_url().toString(), QString(), item);
+ if (podcast.ImageUrlSmall().isValid()) {
+ icon_loader_->LoadIcon(podcast.ImageUrlSmall().toString(), QString(), item);
}
return item;