mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-30 11:04:57 +01:00
Parse OPML documents
This commit is contained in:
parent
8a2e282676
commit
d48177d630
@ -60,7 +60,15 @@ void AddPodcastByUrl::RequestFinished(PodcastUrlLoaderReply* reply) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (const Podcast& podcast, reply->results()) {
|
switch (reply->result_type()) {
|
||||||
model()->appendRow(model()->CreatePodcastItem(podcast));
|
case PodcastUrlLoaderReply::Type_Podcast:
|
||||||
|
foreach (const Podcast& podcast, reply->podcast_results()) {
|
||||||
|
model()->appendRow(model()->CreatePodcastItem(podcast));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PodcastUrlLoaderReply::Type_Opml:
|
||||||
|
model()->CreateOpmlContainerItems(reply->opml_results(), model()->invisibleRootItem());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,6 @@ void AddPodcastDialog::ChangePage(int index) {
|
|||||||
ui_->stack->setCurrentIndex(index);
|
ui_->stack->setCurrentIndex(index);
|
||||||
ui_->stack->setVisible(page->has_visible_widget());
|
ui_->stack->setVisible(page->has_visible_widget());
|
||||||
ui_->results->setModel(page->model());
|
ui_->results->setModel(page->model());
|
||||||
ui_->results->setRootIsDecorated(page->model()->is_tree());
|
|
||||||
|
|
||||||
ui_->results_stack->setCurrentWidget(
|
ui_->results_stack->setCurrentWidget(
|
||||||
page_is_busy_[index] ? ui_->busy_page : ui_->results_page);
|
page_is_busy_[index] ? ui_->busy_page : ui_->results_page);
|
||||||
@ -98,25 +97,17 @@ void AddPodcastDialog::ChangePage(int index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AddPodcastDialog::ChangePodcast(const QModelIndex& current) {
|
void AddPodcastDialog::ChangePodcast(const QModelIndex& current) {
|
||||||
|
QVariant podcast_variant = current.data(PodcastDiscoveryModel::Role_Podcast);
|
||||||
|
|
||||||
// If the selected item is invalid or not a podcast, hide the details pane.
|
// If the selected item is invalid or not a podcast, hide the details pane.
|
||||||
if (!current.isValid() ||
|
if (podcast_variant.isNull()) {
|
||||||
current.data(PodcastDiscoveryModel::Role_Type).toInt() !=
|
|
||||||
PodcastDiscoveryModel::Type_Podcast) {
|
|
||||||
ui_->details_scroll_area->hide();
|
ui_->details_scroll_area->hide();
|
||||||
add_button_->setEnabled(false);
|
add_button_->setEnabled(false);
|
||||||
remove_button_->setEnabled(false);
|
remove_button_->setEnabled(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
current_podcast_ = current.data(PodcastDiscoveryModel::Role_Podcast).value<Podcast>();
|
current_podcast_ = podcast_variant.value<Podcast>();
|
||||||
|
|
||||||
// Also hide the details pane if this podcast isn't valid.
|
|
||||||
if (!current_podcast_.url().isValid()) {
|
|
||||||
ui_->details_scroll_area->hide();
|
|
||||||
add_button_->setEnabled(false);
|
|
||||||
remove_button_->setEnabled(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the blur+fade if there's already a podcast in the details pane.
|
// Start the blur+fade if there's already a podcast in the details pane.
|
||||||
if (ui_->details_scroll_area->isVisible()) {
|
if (ui_->details_scroll_area->isVisible()) {
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QStackedWidget" name="results_stack">
|
<widget class="QStackedWidget" name="results_stack">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>1</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="results_page">
|
<widget class="QWidget" name="results_page">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
@ -229,8 +229,8 @@
|
|||||||
<slot>accept()</slot>
|
<slot>accept()</slot>
|
||||||
<hints>
|
<hints>
|
||||||
<hint type="sourcelabel">
|
<hint type="sourcelabel">
|
||||||
<x>827</x>
|
<x>836</x>
|
||||||
<y>436</y>
|
<y>463</y>
|
||||||
</hint>
|
</hint>
|
||||||
<hint type="destinationlabel">
|
<hint type="destinationlabel">
|
||||||
<x>157</x>
|
<x>157</x>
|
||||||
@ -245,8 +245,8 @@
|
|||||||
<slot>reject()</slot>
|
<slot>reject()</slot>
|
||||||
<hints>
|
<hints>
|
||||||
<hint type="sourcelabel">
|
<hint type="sourcelabel">
|
||||||
<x>876</x>
|
<x>885</x>
|
||||||
<y>436</y>
|
<y>463</y>
|
||||||
</hint>
|
</hint>
|
||||||
<hint type="destinationlabel">
|
<hint type="destinationlabel">
|
||||||
<x>286</x>
|
<x>286</x>
|
||||||
|
@ -29,7 +29,6 @@ GPodderTopTagsModel::GPodderTopTagsModel(mygpo::ApiRequest* api, Application* ap
|
|||||||
: PodcastDiscoveryModel(app, parent),
|
: PodcastDiscoveryModel(app, parent),
|
||||||
api_(api)
|
api_(api)
|
||||||
{
|
{
|
||||||
set_is_tree(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPodderTopTagsModel::hasChildren(const QModelIndex& parent) const {
|
bool GPodderTopTagsModel::hasChildren(const QModelIndex& parent) const {
|
||||||
|
@ -47,5 +47,22 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<include location="../../data/data.qrc"/>
|
<include location="../../data/data.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
<connections/>
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>query</sender>
|
||||||
|
<signal>returnPressed()</signal>
|
||||||
|
<receiver>search</receiver>
|
||||||
|
<slot>click()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>237</x>
|
||||||
|
<y>52</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>461</x>
|
||||||
|
<y>55</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
</ui>
|
</ui>
|
||||||
|
31
src/podcasts/opmlcontainer.h
Normal file
31
src/podcasts/opmlcontainer.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/* 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 OPMLCONTAINER_H
|
||||||
|
#define OPMLCONTAINER_H
|
||||||
|
|
||||||
|
#include "podcast.h"
|
||||||
|
|
||||||
|
struct OpmlContainer {
|
||||||
|
QString name;
|
||||||
|
QList<OpmlContainer> containers;
|
||||||
|
PodcastList feeds;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(OpmlContainer)
|
||||||
|
|
||||||
|
#endif // OPMLCONTAINER_H
|
@ -15,6 +15,7 @@
|
|||||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "opmlcontainer.h"
|
||||||
#include "podcast.h"
|
#include "podcast.h"
|
||||||
#include "podcastdiscoverymodel.h"
|
#include "podcastdiscoverymodel.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
@ -28,18 +29,18 @@ PodcastDiscoveryModel::PodcastDiscoveryModel(Application* app, QObject* parent)
|
|||||||
: QStandardItemModel(parent),
|
: QStandardItemModel(parent),
|
||||||
app_(app),
|
app_(app),
|
||||||
icon_loader_(new StandardItemIconLoader(app->album_cover_loader(), this)),
|
icon_loader_(new StandardItemIconLoader(app->album_cover_loader(), this)),
|
||||||
is_tree_(false),
|
|
||||||
default_icon_(":providers/podcast16.png")
|
default_icon_(":providers/podcast16.png")
|
||||||
{
|
{
|
||||||
icon_loader_->SetModel(this);
|
icon_loader_->SetModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant PodcastDiscoveryModel::data(const QModelIndex& index, int role) const {
|
QVariant PodcastDiscoveryModel::data(const QModelIndex& index, int role) const {
|
||||||
if (index.isValid() &&
|
if (index.isValid() && role == Qt::DecorationRole &&
|
||||||
role == Qt::DecorationRole &&
|
|
||||||
QStandardItemModel::data(index, Role_Type).toInt() == Type_Podcast &&
|
|
||||||
QStandardItemModel::data(index, Role_StartedLoadingImage).toBool() == false) {
|
QStandardItemModel::data(index, Role_StartedLoadingImage).toBool() == false) {
|
||||||
const_cast<PodcastDiscoveryModel*>(this)->LazyLoadImage(index);
|
const QUrl image_url = QStandardItemModel::data(index, Role_ImageUrl).toUrl();
|
||||||
|
if (image_url.isValid()) {
|
||||||
|
const_cast<PodcastDiscoveryModel*>(this)->LazyLoadImage(image_url, index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QStandardItemModel::data(index, role);
|
return QStandardItemModel::data(index, role);
|
||||||
@ -51,6 +52,7 @@ QStandardItem* PodcastDiscoveryModel::CreatePodcastItem(const Podcast& podcast)
|
|||||||
item->setText(podcast.title());
|
item->setText(podcast.title());
|
||||||
item->setData(QVariant::fromValue(podcast), Role_Podcast);
|
item->setData(QVariant::fromValue(podcast), Role_Podcast);
|
||||||
item->setData(Type_Podcast, Role_Type);
|
item->setData(Type_Podcast, Role_Type);
|
||||||
|
item->setData(podcast.ImageUrlSmall(), Role_ImageUrl);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,15 +68,28 @@ QStandardItem* PodcastDiscoveryModel::CreateFolder(const QString& name) {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PodcastDiscoveryModel::LazyLoadImage(const QModelIndex& index) {
|
QStandardItem* PodcastDiscoveryModel::CreateOpmlContainerItem(const OpmlContainer& container) {
|
||||||
|
QStandardItem* item = CreateFolder(container.name);
|
||||||
|
CreateOpmlContainerItems(container, item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PodcastDiscoveryModel::CreateOpmlContainerItems(const OpmlContainer& container, QStandardItem* parent) {
|
||||||
|
foreach (const OpmlContainer& child, container.containers) {
|
||||||
|
QStandardItem* child_item = CreateOpmlContainerItem(child);
|
||||||
|
parent->appendRow(child_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (const Podcast& child, container.feeds) {
|
||||||
|
QStandardItem* child_item = CreatePodcastItem(child);
|
||||||
|
parent->appendRow(child_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PodcastDiscoveryModel::LazyLoadImage(const QUrl& url, const QModelIndex& index) {
|
||||||
QStandardItem* item = itemFromIndex(index);
|
QStandardItem* item = itemFromIndex(index);
|
||||||
item->setData(true, Role_StartedLoadingImage);
|
item->setData(true, Role_StartedLoadingImage);
|
||||||
|
icon_loader_->LoadIcon(url.toString(), QString(), item);
|
||||||
Podcast podcast = index.data(Role_Podcast).value<Podcast>();
|
|
||||||
|
|
||||||
if (podcast.ImageUrlSmall().isValid()) {
|
|
||||||
icon_loader_->LoadIcon(podcast.ImageUrlSmall().toString(), QString(), item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStandardItem* PodcastDiscoveryModel::CreateLoadingIndicator() {
|
QStandardItem* PodcastDiscoveryModel::CreateLoadingIndicator() {
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
|
class OpmlContainer;
|
||||||
|
class OpmlFeed;
|
||||||
class Podcast;
|
class Podcast;
|
||||||
class StandardItemIconLoader;
|
class StandardItemIconLoader;
|
||||||
|
|
||||||
@ -41,14 +43,14 @@ public:
|
|||||||
enum Role {
|
enum Role {
|
||||||
Role_Podcast = Qt::UserRole,
|
Role_Podcast = Qt::UserRole,
|
||||||
Role_Type,
|
Role_Type,
|
||||||
|
Role_ImageUrl,
|
||||||
Role_StartedLoadingImage,
|
Role_StartedLoadingImage,
|
||||||
|
|
||||||
RoleCount
|
RoleCount
|
||||||
};
|
};
|
||||||
|
|
||||||
bool is_tree() const { return is_tree_; }
|
void CreateOpmlContainerItems(const OpmlContainer& container, QStandardItem* parent);
|
||||||
void set_is_tree(bool v) { is_tree_ = v; }
|
QStandardItem* CreateOpmlContainerItem(const OpmlContainer& container);
|
||||||
|
|
||||||
QStandardItem* CreatePodcastItem(const Podcast& podcast);
|
QStandardItem* CreatePodcastItem(const Podcast& podcast);
|
||||||
QStandardItem* CreateFolder(const QString& name);
|
QStandardItem* CreateFolder(const QString& name);
|
||||||
QStandardItem* CreateLoadingIndicator();
|
QStandardItem* CreateLoadingIndicator();
|
||||||
@ -56,14 +58,12 @@ public:
|
|||||||
QVariant data(const QModelIndex& index, int role) const;
|
QVariant data(const QModelIndex& index, int role) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void LazyLoadImage(const QModelIndex& index);
|
void LazyLoadImage(const QUrl& url, const QModelIndex& index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application* app_;
|
Application* app_;
|
||||||
StandardItemIconLoader* icon_loader_;
|
StandardItemIconLoader* icon_loader_;
|
||||||
|
|
||||||
bool is_tree_;
|
|
||||||
|
|
||||||
QIcon default_icon_;
|
QIcon default_icon_;
|
||||||
QIcon folder_icon_;
|
QIcon folder_icon_;
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "opmlcontainer.h"
|
||||||
#include "podcastparser.h"
|
#include "podcastparser.h"
|
||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ const char* PodcastParser::kItunesNamespace = "http://www.itunes.com/dtds/podcas
|
|||||||
PodcastParser::PodcastParser() {
|
PodcastParser::PodcastParser() {
|
||||||
supported_mime_types_ << "application/rss+xml"
|
supported_mime_types_ << "application/rss+xml"
|
||||||
<< "application/xml"
|
<< "application/xml"
|
||||||
|
<< "text/x-opml"
|
||||||
<< "text/xml";
|
<< "text/xml";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,16 +41,47 @@ bool PodcastParser::SupportsContentType(const QString& content_type) const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PodcastParser::Load(QIODevice* device, const QUrl& url, Podcast* ret) const {
|
QVariant PodcastParser::Load(QIODevice* device, const QUrl& url) const {
|
||||||
ret->set_url(url);
|
|
||||||
|
|
||||||
QXmlStreamReader reader(device);
|
QXmlStreamReader reader(device);
|
||||||
if (!Utilities::ParseUntilElement(&reader, "rss") ||
|
|
||||||
!Utilities::ParseUntilElement(&reader, "channel")) {
|
while (!reader.atEnd()) {
|
||||||
|
switch (reader.readNext()) {
|
||||||
|
case QXmlStreamReader::StartElement: {
|
||||||
|
const QStringRef name = reader.name();
|
||||||
|
if (name == "rss") {
|
||||||
|
Podcast podcast;
|
||||||
|
if (!ParseRss(&reader, &podcast)) {
|
||||||
|
return QVariant();
|
||||||
|
} else {
|
||||||
|
podcast.set_url(url);
|
||||||
|
return QVariant::fromValue(podcast);
|
||||||
|
}
|
||||||
|
} else if (name == "opml") {
|
||||||
|
OpmlContainer container;
|
||||||
|
if (!ParseOpml(&reader, &container)) {
|
||||||
|
return QVariant();
|
||||||
|
} else {
|
||||||
|
return QVariant::fromValue(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PodcastParser::ParseRss(QXmlStreamReader* reader, Podcast* ret) const {
|
||||||
|
if (!Utilities::ParseUntilElement(reader, "channel")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseChannel(&reader, ret);
|
ParseChannel(reader, ret);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,3 +218,70 @@ void PodcastParser::ParseItem(QXmlStreamReader* reader, Podcast* ret) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PodcastParser::ParseOpml(QXmlStreamReader* reader, OpmlContainer* ret) const {
|
||||||
|
if (!Utilities::ParseUntilElement(reader, "body")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseOutline(reader, ret);
|
||||||
|
|
||||||
|
// OPML files sometimes consist of a single top level container.
|
||||||
|
while (ret->feeds.count() == 0 &&
|
||||||
|
ret->containers.count() == 1) {
|
||||||
|
*ret = ret->containers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PodcastParser::ParseOutline(QXmlStreamReader* reader, OpmlContainer* ret) const {
|
||||||
|
while (!reader->atEnd()) {
|
||||||
|
QXmlStreamReader::TokenType type = reader->readNext();
|
||||||
|
switch (type) {
|
||||||
|
case QXmlStreamReader::StartElement: {
|
||||||
|
const QStringRef name = reader->name();
|
||||||
|
if (name != "outline") {
|
||||||
|
Utilities::ConsumeCurrentElement(reader);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmlStreamAttributes attributes = reader->attributes();
|
||||||
|
|
||||||
|
if (attributes.value("type").toString() == "rss") {
|
||||||
|
// Parse the feed and add it to this container
|
||||||
|
Podcast podcast;
|
||||||
|
podcast.set_description(attributes.value("description").toString());
|
||||||
|
podcast.set_title(attributes.value("text").toString());
|
||||||
|
podcast.set_image_url_large(QUrl(attributes.value("imageHref").toString()));
|
||||||
|
podcast.set_url(QUrl(attributes.value("xmlUrl").toString()));
|
||||||
|
ret->feeds.append(podcast);
|
||||||
|
|
||||||
|
// Consume any children and the EndElement.
|
||||||
|
Utilities::ConsumeCurrentElement(reader);
|
||||||
|
} else {
|
||||||
|
// Create a new child container
|
||||||
|
OpmlContainer child;
|
||||||
|
|
||||||
|
// Take the name from the fullname attribute first if it exists.
|
||||||
|
child.name = attributes.value("fullname").toString();
|
||||||
|
if (child.name.isEmpty()) {
|
||||||
|
child.name = attributes.value("text").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse its contents and add it to this container
|
||||||
|
ParseOutline(reader, &child);
|
||||||
|
ret->containers.append(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case QXmlStreamReader::EndElement:
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,9 +22,13 @@
|
|||||||
|
|
||||||
#include "podcast.h"
|
#include "podcast.h"
|
||||||
|
|
||||||
|
class OpmlContainer;
|
||||||
|
|
||||||
class QXmlStreamReader;
|
class QXmlStreamReader;
|
||||||
|
|
||||||
// Reads XML data from a QIODevice and returns a Podcast.
|
// Reads XML data from a QIODevice.
|
||||||
|
// Returns either a Podcast or an OpmlContainer depending on what was inside
|
||||||
|
// the XML document.
|
||||||
class PodcastParser {
|
class PodcastParser {
|
||||||
public:
|
public:
|
||||||
PodcastParser();
|
PodcastParser();
|
||||||
@ -35,14 +39,21 @@ public:
|
|||||||
const QStringList& supported_mime_types() const { return supported_mime_types_; }
|
const QStringList& supported_mime_types() const { return supported_mime_types_; }
|
||||||
bool SupportsContentType(const QString& content_type) const;
|
bool SupportsContentType(const QString& content_type) const;
|
||||||
|
|
||||||
bool Load(QIODevice* device, const QUrl& url, Podcast* ret) const;
|
// You should check the type of the returned QVariant to see whether it
|
||||||
|
// contains a Podcast or an OpmlContainer. If the QVariant isNull then an
|
||||||
|
// error occurred parsing the XML.
|
||||||
|
QVariant Load(QIODevice* device, const QUrl& url) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool ParseRss(QXmlStreamReader* reader, Podcast* ret) const;
|
||||||
void ParseChannel(QXmlStreamReader* reader, Podcast* ret) const;
|
void ParseChannel(QXmlStreamReader* reader, Podcast* ret) const;
|
||||||
void ParseImage(QXmlStreamReader* reader, Podcast* ret) const;
|
void ParseImage(QXmlStreamReader* reader, Podcast* ret) const;
|
||||||
void ParseItunesOwner(QXmlStreamReader* reader, Podcast* ret) const;
|
void ParseItunesOwner(QXmlStreamReader* reader, Podcast* ret) const;
|
||||||
void ParseItem(QXmlStreamReader* reader, Podcast* ret) const;
|
void ParseItem(QXmlStreamReader* reader, Podcast* ret) const;
|
||||||
|
|
||||||
|
bool ParseOpml(QXmlStreamReader* reader, OpmlContainer* ret) const;
|
||||||
|
void ParseOutline(QXmlStreamReader* reader, OpmlContainer* ret) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QStringList supported_mime_types_;
|
QStringList supported_mime_types_;
|
||||||
};
|
};
|
||||||
|
@ -145,13 +145,17 @@ void PodcastUrlLoader::RequestFinished(RequestState* state, QNetworkReply* reply
|
|||||||
// Check the mime type.
|
// Check the mime type.
|
||||||
const QString content_type = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
const QString content_type = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||||
if (parser_->SupportsContentType(content_type)) {
|
if (parser_->SupportsContentType(content_type)) {
|
||||||
Podcast podcast;
|
const QVariant ret = parser_->Load(reply, reply->url());
|
||||||
if (!parser_->Load(reply, reply->url(), &podcast)) {
|
|
||||||
|
if (ret.canConvert<Podcast>()) {
|
||||||
|
state->reply_->SetFinished(PodcastList() << ret.value<Podcast>());
|
||||||
|
} else if (ret.canConvert<OpmlContainer>()) {
|
||||||
|
state->reply_->SetFinished(ret.value<OpmlContainer>());
|
||||||
|
} else {
|
||||||
SendErrorAndDelete(tr("Failed to parse the XML for this RSS feed"), state);
|
SendErrorAndDelete(tr("Failed to parse the XML for this RSS feed"), state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->reply_->SetFinished(PodcastList() << podcast);
|
|
||||||
delete state;
|
delete state;
|
||||||
return;
|
return;
|
||||||
} else if (content_type.contains("text/html")) {
|
} else if (content_type.contains("text/html")) {
|
||||||
@ -192,7 +196,15 @@ PodcastUrlLoaderReply::PodcastUrlLoaderReply(const QUrl& url, QObject* parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PodcastUrlLoaderReply::SetFinished(const PodcastList& results) {
|
void PodcastUrlLoaderReply::SetFinished(const PodcastList& results) {
|
||||||
results_ = results;
|
result_type_ = Type_Podcast;
|
||||||
|
podcast_results_ = results;
|
||||||
|
finished_ = true;
|
||||||
|
emit Finished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PodcastUrlLoaderReply::SetFinished(const OpmlContainer& results) {
|
||||||
|
result_type_ = Type_Opml;
|
||||||
|
opml_results_ = results;
|
||||||
finished_ = true;
|
finished_ = true;
|
||||||
emit Finished(true);
|
emit Finished(true);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
|
|
||||||
|
#include "opmlcontainer.h"
|
||||||
#include "podcast.h"
|
#include "podcast.h"
|
||||||
|
|
||||||
class PodcastParser;
|
class PodcastParser;
|
||||||
@ -34,14 +35,23 @@ class PodcastUrlLoaderReply : public QObject {
|
|||||||
public:
|
public:
|
||||||
PodcastUrlLoaderReply(const QUrl& url, QObject* parent);
|
PodcastUrlLoaderReply(const QUrl& url, QObject* parent);
|
||||||
|
|
||||||
|
enum ResultType {
|
||||||
|
Type_Podcast,
|
||||||
|
Type_Opml
|
||||||
|
};
|
||||||
|
|
||||||
const QUrl& url() const { return url_; }
|
const QUrl& url() const { return url_; }
|
||||||
bool is_finished() const { return finished_; }
|
bool is_finished() const { return finished_; }
|
||||||
bool is_success() const { return error_text_.isEmpty(); }
|
bool is_success() const { return error_text_.isEmpty(); }
|
||||||
const QString& error_text() const { return error_text_; }
|
const QString& error_text() const { return error_text_; }
|
||||||
const PodcastList& results() const { return results_; }
|
|
||||||
|
ResultType result_type() const { return result_type_; }
|
||||||
|
const PodcastList& podcast_results() const { return podcast_results_; }
|
||||||
|
const OpmlContainer& opml_results() const { return opml_results_; }
|
||||||
|
|
||||||
void SetFinished(const QString& error_text);
|
void SetFinished(const QString& error_text);
|
||||||
void SetFinished(const PodcastList& results);
|
void SetFinished(const PodcastList& results);
|
||||||
|
void SetFinished(const OpmlContainer& results);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void Finished(bool success);
|
void Finished(bool success);
|
||||||
@ -50,7 +60,10 @@ private:
|
|||||||
QUrl url_;
|
QUrl url_;
|
||||||
bool finished_;
|
bool finished_;
|
||||||
QString error_text_;
|
QString error_text_;
|
||||||
PodcastList results_;
|
|
||||||
|
ResultType result_type_;
|
||||||
|
PodcastList podcast_results_;
|
||||||
|
OpmlContainer opml_results_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user