Import made better - ability to fetch feed metadata.

This commit is contained in:
Martin Rotter 2016-01-18 11:20:16 +01:00
parent 785b1a344c
commit 55dadfe238
6 changed files with 99 additions and 46 deletions

View File

@ -3,6 +3,7 @@
Added:
▪ Import of OPML/TXT files now allows to fetch feed metadata from online feed source.
▪ Added generic "Add new feed" action, which can be accessed via "Feeds & messages" menu. (issue #146)
▪ User can now specify destination root node when importing feeds. (issue #147)
▪ Added support for import/export to/from plain TXT file (one feed URL per line). (issue #142)

View File

@ -24,6 +24,7 @@
#include "miscellaneous/application.h"
#include "gui/feedmessageviewer.h"
#include "gui/feedsview.h"
#include "gui/messagebox.h"
#include "gui/dialogs/formmain.h"
#include "exceptions/ioexception.h"
@ -91,7 +92,7 @@ void FormStandardImportExport::setMode(const FeedsImportExportModel::Mode &mode)
break;
}
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true);
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
void FormStandardImportExport::selectFile() {
@ -113,8 +114,11 @@ void FormStandardImportExport::selectFile() {
void FormStandardImportExport::onParsingStarted() {
m_ui->m_lblResult->setStatus(WidgetWithStatus::Progress, tr("Parsing data..."), tr("Parsing data..."));
m_ui->m_btnSelectFile->setEnabled(false);
m_ui->m_groupFeeds->setEnabled(false);
m_ui->m_progressBar->setValue(0);
m_ui->m_progressBar->setVisible(true);
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
void FormStandardImportExport::onParsingFinished(int count_failed, int count_succeeded, bool parsing_error) {
@ -138,7 +142,7 @@ void FormStandardImportExport::onParsingFinished(int count_failed, int count_suc
tr("Error occurred. File is not well-formed. Select another file."));
}
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!parsing_error);
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
void FormStandardImportExport::onParsingProgress(int completed, int total) {
@ -180,7 +184,7 @@ void FormStandardImportExport::selectExportFile() {
m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::Ok, QDir::toNativeSeparators(selected_file), tr("File is selected."));
}
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setDisabled(selected_file.isEmpty());
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_ui->m_lblSelectFile->status() == WidgetWithStatus::Ok);
}
void FormStandardImportExport::selectImportFile() {
@ -208,11 +212,17 @@ void FormStandardImportExport::selectImportFile() {
m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::Ok, QDir::toNativeSeparators(selected_file), tr("File is selected."));
parseImportFile(selected_file);
QMessageBox::StandardButton answer = MessageBox::show(this, QMessageBox::Warning, tr("Get online metadata"),
tr("Metadata for your feeds can be fetched online. Note that the action "
"could take several minutes, depending on number of feeds."),
tr("Do you want to fetch feed metadata online?"), QString(), QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
parseImportFile(selected_file, answer == QMessageBox::Yes);
}
}
void FormStandardImportExport::parseImportFile(const QString &file_name) {
void FormStandardImportExport::parseImportFile(const QString &file_name, bool fetch_metadata_online) {
QFile input_file(file_name);
QByteArray input_data;
@ -227,18 +237,13 @@ void FormStandardImportExport::parseImportFile(const QString &file_name) {
switch (m_conversionType) {
case OPML20:
m_model->importAsOPML20(input_data);
m_model->importAsOPML20(input_data, fetch_metadata_online);
break;
case TXTUrlPerLine:
m_model->importAsTxtURLPerLine(input_data);
m_model->importAsTxtURLPerLine(input_data, fetch_metadata_online);
break;
// TODO: V celém kódu nově zavést pořádně všude const, i v lokálních metodových proměnných
// TODO: Kompletně nahradit všechny ukazatele za QScopedPointer tak,
// aby se nikde v kodu nevolalo delete či deleteLater().
default:
return;
}

View File

@ -56,7 +56,7 @@ class FormStandardImportExport : public QDialog {
private:
void selectExportFile();
void selectImportFile();
void parseImportFile(const QString &file_name);
void parseImportFile(const QString &file_name, bool fetch_metadata_online);
void exportFeeds();
void importFeeds();

View File

@ -58,6 +58,10 @@ RootItem *FeedsImportExportModel::rootItem() const {
}
void FeedsImportExportModel::setRootItem(RootItem *root_item) {
if (m_rootItem != NULL) {
delete m_rootItem;
}
m_rootItem = root_item;
}
@ -154,8 +158,11 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray &result) {
return true;
}
void FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
void FeedsImportExportModel::importAsOPML20(const QByteArray &data, bool fetch_metadata_online) {
emit parsingStarted();
emit layoutAboutToBeChanged();
setRootItem(NULL);
emit layoutChanged();
QDomDocument opml_document;
@ -169,7 +176,7 @@ void FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
emit parsingFinished(0, 0, true);
}
int completed = 0, total = 0;
int completed = 0, total = 0, succeded = 0, failed = 0;
StandardServiceRoot *root_item = new StandardServiceRoot();
QStack<RootItem*> model_items; model_items.push(root_item);
QStack<QDomElement> elements_to_process; elements_to_process.push(opml_document.documentElement().elementsByTagName(QSL("body")).at(0).toElement());
@ -192,32 +199,54 @@ void FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
if (child_element.attributes().contains(QSL("xmlUrl")) && child.attributes().contains(QSL("text"))) {
// This is FEED.
// Add feed and end this iteration.
QString feed_title = child_element.attribute(QSL("text"));
QString feed_url = child_element.attribute(QSL("xmlUrl"));
QString feed_encoding = child_element.attribute(QSL("encoding"), DEFAULT_FEED_ENCODING);
QString feed_type = child_element.attribute(QSL("version"), DEFAULT_FEED_TYPE).toUpper();
QString feed_description = child_element.attribute(QSL("description"));
QIcon feed_icon = qApp->icons()->fromByteArray(child_element.attribute(QSL("rssguard:icon")).toLocal8Bit());
StandardFeed *new_feed = new StandardFeed(active_model_item);
new_feed->setTitle(feed_title);
new_feed->setDescription(feed_description);
new_feed->setEncoding(feed_encoding);
new_feed->setUrl(feed_url);
new_feed->setCreationDate(QDateTime::currentDateTime());
new_feed->setIcon(feed_icon.isNull() ? qApp->icons()->fromTheme(QSL("folder-feed")) : feed_icon);
if (!feed_url.isEmpty()) {
QPair<StandardFeed*,QNetworkReply::NetworkError> guessed;
if (feed_type == QL1S("RSS1")) {
new_feed->setType(StandardFeed::Rdf);
}
else if (feed_type == QL1S("ATOM")) {
new_feed->setType(StandardFeed::Atom10);
}
else {
new_feed->setType(StandardFeed::Rss2X);
}
if (fetch_metadata_online &&
(guessed = StandardFeed::guessFeed(feed_url)).second == QNetworkReply::NoError) {
// We should obtain fresh metadata from online feed source.
guessed.first->setUrl(feed_url);
active_model_item->appendChild(guessed.first);
active_model_item->appendChild(new_feed);
succeded++;
}
else {
QString feed_title = child_element.attribute(QSL("text"));
QString feed_encoding = child_element.attribute(QSL("encoding"), DEFAULT_FEED_ENCODING);
QString feed_type = child_element.attribute(QSL("version"), DEFAULT_FEED_TYPE).toUpper();
QString feed_description = child_element.attribute(QSL("description"));
QIcon feed_icon = qApp->icons()->fromByteArray(child_element.attribute(QSL("rssguard:icon")).toLocal8Bit());
StandardFeed *new_feed = new StandardFeed(active_model_item);
new_feed->setTitle(feed_title);
new_feed->setDescription(feed_description);
new_feed->setEncoding(feed_encoding);
new_feed->setUrl(feed_url);
new_feed->setCreationDate(QDateTime::currentDateTime());
new_feed->setIcon(feed_icon.isNull() ? qApp->icons()->fromTheme(QSL("folder-feed")) : feed_icon);
if (feed_type == QL1S("RSS1")) {
new_feed->setType(StandardFeed::Rdf);
}
else if (feed_type == QL1S("ATOM")) {
new_feed->setType(StandardFeed::Atom10);
}
else {
new_feed->setType(StandardFeed::Rss2X);
}
active_model_item->appendChild(new_feed);
if (fetch_metadata_online && guessed.second != QNetworkReply::NoError) {
failed++;
}
else {
succeded++;
}
}
}
}
else {
// This must be CATEGORY.
@ -258,7 +287,7 @@ void FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
emit layoutAboutToBeChanged();
setRootItem(root_item);
emit layoutChanged();
emit parsingFinished(0, completed, false);
emit parsingFinished(failed, succeded, false);
}
bool FeedsImportExportModel::exportToTxtURLPerLine(QByteArray &result) {
@ -269,8 +298,11 @@ bool FeedsImportExportModel::exportToTxtURLPerLine(QByteArray &result) {
return true;
}
void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data) {
void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data, bool fetch_metadata_online) {
emit parsingStarted();
emit layoutAboutToBeChanged();
setRootItem(NULL);
emit layoutChanged();
int completed = 0, succeded = 0, failed = 0;
StandardServiceRoot *root_item = new StandardServiceRoot();
@ -278,9 +310,11 @@ void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data) {
foreach (const QByteArray &url, urls) {
if (!url.isEmpty()) {
QPair<StandardFeed*,QNetworkReply::NetworkError> guessed = StandardFeed::guessFeed(url);
QPair<StandardFeed*,QNetworkReply::NetworkError> guessed;
if (guessed.second == QNetworkReply::NoError) {
if (fetch_metadata_online &&
(guessed = StandardFeed::guessFeed(url)).second == QNetworkReply::NoError) {
guessed.first->setUrl(url);
root_item->appendChild(guessed.first);
succeded++;
@ -294,7 +328,13 @@ void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data) {
feed->setIcon(qApp->icons()->fromTheme(QSL("folder-feed")));
feed->setEncoding(DEFAULT_FEED_ENCODING);
root_item->appendChild(feed);
failed++;
if (fetch_metadata_online && guessed.second != QNetworkReply::NoError) {
failed++;
}
else {
succeded++;
}
}
qApp->processEvents();
@ -420,7 +460,14 @@ int FeedsImportExportModel::rowCount(const QModelIndex &parent) const {
return 0;
}
else {
return itemForIndex(parent)->childCount();
RootItem *item = itemForIndex(parent);
if (item != NULL) {
return item->childCount();
}
else {
return 0;
}
}
}

View File

@ -60,12 +60,12 @@ class FeedsImportExportModel : public QAbstractItemModel {
// Exports to OPML 2.0
// NOTE: http://dev.opml.org/spec2.html
bool exportToOMPL20(QByteArray &result);
void importAsOPML20(const QByteArray &data);
void importAsOPML20(const QByteArray &data, bool fetch_metadata_online);
// Exports to plain text format
// where there is one feed URL per line.
bool exportToTxtURLPerLine(QByteArray &result);
void importAsTxtURLPerLine(const QByteArray &data);
void importAsTxtURLPerLine(const QByteArray &data, bool fetch_metadata_online);
Mode mode() const;
void setMode(const Mode &mode);

View File

@ -84,7 +84,7 @@ void StandardServiceRoot::start(bool freshly_activated) {
QString output_msg;
try {
model.importAsOPML20(IOFactory::readTextFile(file_to_load));
model.importAsOPML20(IOFactory::readTextFile(file_to_load), false);
model.checkAllItems();
if (mergeImportExportModel(&model, this, output_msg)) {