mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-31 03:27:40 +01:00
Add an iTunes podcast search page
This commit is contained in:
parent
628820917d
commit
17dfc99462
@ -341,5 +341,6 @@
|
||||
<file>providers/podcast16.png</file>
|
||||
<file>providers/podcast32.png</file>
|
||||
<file>providers/mygpo32.png</file>
|
||||
<file>providers/itunes.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
data/providers/itunes.png
Normal file
BIN
data/providers/itunes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
103
src/podcasts/itunessearchpage.cpp
Normal file
103
src/podcasts/itunessearchpage.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "itunessearchpage.h"
|
||||
#include "podcast.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "ui_itunessearchpage.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
47
src/podcasts/itunessearchpage.h
Normal file
47
src/podcasts/itunessearchpage.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef 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
|
51
src/podcasts/itunessearchpage.ui
Normal file
51
src/podcasts/itunessearchpage.ui
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ITunesSearchPage</class>
|
||||
<widget class="QWidget" name="ITunesSearchPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>516</width>
|
||||
<height>69</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Search iTunes</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/providers/itunes.png</normaloff>:/providers/itunes.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Enter search terms below to find podcasts in the iTunes Store</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="query"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="search">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -72,8 +72,8 @@ void PodcastDiscoveryModel::LazyLoadImage(const QModelIndex& index) {
|
||||
|
||||
Podcast podcast = index.data(Role_Podcast).value<Podcast>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user