Import made better - ability to fetch feed metadata.
This commit is contained in:
parent
785b1a344c
commit
55dadfe238
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
Added:
|
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)
|
▪ 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)
|
▪ 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)
|
▪ Added support for import/export to/from plain TXT file (one feed URL per line). (issue #142)
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "miscellaneous/application.h"
|
#include "miscellaneous/application.h"
|
||||||
#include "gui/feedmessageviewer.h"
|
#include "gui/feedmessageviewer.h"
|
||||||
#include "gui/feedsview.h"
|
#include "gui/feedsview.h"
|
||||||
|
#include "gui/messagebox.h"
|
||||||
#include "gui/dialogs/formmain.h"
|
#include "gui/dialogs/formmain.h"
|
||||||
#include "exceptions/ioexception.h"
|
#include "exceptions/ioexception.h"
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ void FormStandardImportExport::setMode(const FeedsImportExportModel::Mode &mode)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true);
|
m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FormStandardImportExport::selectFile() {
|
void FormStandardImportExport::selectFile() {
|
||||||
@ -113,8 +114,11 @@ void FormStandardImportExport::selectFile() {
|
|||||||
void FormStandardImportExport::onParsingStarted() {
|
void FormStandardImportExport::onParsingStarted() {
|
||||||
m_ui->m_lblResult->setStatus(WidgetWithStatus::Progress, tr("Parsing data..."), tr("Parsing data..."));
|
m_ui->m_lblResult->setStatus(WidgetWithStatus::Progress, tr("Parsing data..."), tr("Parsing data..."));
|
||||||
m_ui->m_btnSelectFile->setEnabled(false);
|
m_ui->m_btnSelectFile->setEnabled(false);
|
||||||
|
m_ui->m_groupFeeds->setEnabled(false);
|
||||||
m_ui->m_progressBar->setValue(0);
|
m_ui->m_progressBar->setValue(0);
|
||||||
m_ui->m_progressBar->setVisible(true);
|
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) {
|
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."));
|
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) {
|
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_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() {
|
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."));
|
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);
|
QFile input_file(file_name);
|
||||||
QByteArray input_data;
|
QByteArray input_data;
|
||||||
|
|
||||||
@ -227,18 +237,13 @@ void FormStandardImportExport::parseImportFile(const QString &file_name) {
|
|||||||
|
|
||||||
switch (m_conversionType) {
|
switch (m_conversionType) {
|
||||||
case OPML20:
|
case OPML20:
|
||||||
m_model->importAsOPML20(input_data);
|
m_model->importAsOPML20(input_data, fetch_metadata_online);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TXTUrlPerLine:
|
case TXTUrlPerLine:
|
||||||
m_model->importAsTxtURLPerLine(input_data);
|
m_model->importAsTxtURLPerLine(input_data, fetch_metadata_online);
|
||||||
break;
|
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:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ class FormStandardImportExport : public QDialog {
|
|||||||
private:
|
private:
|
||||||
void selectExportFile();
|
void selectExportFile();
|
||||||
void selectImportFile();
|
void selectImportFile();
|
||||||
void parseImportFile(const QString &file_name);
|
void parseImportFile(const QString &file_name, bool fetch_metadata_online);
|
||||||
|
|
||||||
void exportFeeds();
|
void exportFeeds();
|
||||||
void importFeeds();
|
void importFeeds();
|
||||||
|
@ -58,6 +58,10 @@ RootItem *FeedsImportExportModel::rootItem() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FeedsImportExportModel::setRootItem(RootItem *root_item) {
|
void FeedsImportExportModel::setRootItem(RootItem *root_item) {
|
||||||
|
if (m_rootItem != NULL) {
|
||||||
|
delete m_rootItem;
|
||||||
|
}
|
||||||
|
|
||||||
m_rootItem = root_item;
|
m_rootItem = root_item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,8 +158,11 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray &result) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
|
void FeedsImportExportModel::importAsOPML20(const QByteArray &data, bool fetch_metadata_online) {
|
||||||
emit parsingStarted();
|
emit parsingStarted();
|
||||||
|
emit layoutAboutToBeChanged();
|
||||||
|
setRootItem(NULL);
|
||||||
|
emit layoutChanged();
|
||||||
|
|
||||||
QDomDocument opml_document;
|
QDomDocument opml_document;
|
||||||
|
|
||||||
@ -169,7 +176,7 @@ void FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
|
|||||||
emit parsingFinished(0, 0, true);
|
emit parsingFinished(0, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
int completed = 0, total = 0;
|
int completed = 0, total = 0, succeded = 0, failed = 0;
|
||||||
StandardServiceRoot *root_item = new StandardServiceRoot();
|
StandardServiceRoot *root_item = new StandardServiceRoot();
|
||||||
QStack<RootItem*> model_items; model_items.push(root_item);
|
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());
|
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"))) {
|
if (child_element.attributes().contains(QSL("xmlUrl")) && child.attributes().contains(QSL("text"))) {
|
||||||
// This is FEED.
|
// This is FEED.
|
||||||
// Add feed and end this iteration.
|
// Add feed and end this iteration.
|
||||||
QString feed_title = child_element.attribute(QSL("text"));
|
|
||||||
QString feed_url = child_element.attribute(QSL("xmlUrl"));
|
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);
|
if (!feed_url.isEmpty()) {
|
||||||
new_feed->setTitle(feed_title);
|
QPair<StandardFeed*,QNetworkReply::NetworkError> guessed;
|
||||||
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")) {
|
if (fetch_metadata_online &&
|
||||||
new_feed->setType(StandardFeed::Rdf);
|
(guessed = StandardFeed::guessFeed(feed_url)).second == QNetworkReply::NoError) {
|
||||||
}
|
// We should obtain fresh metadata from online feed source.
|
||||||
else if (feed_type == QL1S("ATOM")) {
|
guessed.first->setUrl(feed_url);
|
||||||
new_feed->setType(StandardFeed::Atom10);
|
active_model_item->appendChild(guessed.first);
|
||||||
}
|
|
||||||
else {
|
|
||||||
new_feed->setType(StandardFeed::Rss2X);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
else {
|
||||||
// This must be CATEGORY.
|
// This must be CATEGORY.
|
||||||
@ -258,7 +287,7 @@ void FeedsImportExportModel::importAsOPML20(const QByteArray &data) {
|
|||||||
emit layoutAboutToBeChanged();
|
emit layoutAboutToBeChanged();
|
||||||
setRootItem(root_item);
|
setRootItem(root_item);
|
||||||
emit layoutChanged();
|
emit layoutChanged();
|
||||||
emit parsingFinished(0, completed, false);
|
emit parsingFinished(failed, succeded, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FeedsImportExportModel::exportToTxtURLPerLine(QByteArray &result) {
|
bool FeedsImportExportModel::exportToTxtURLPerLine(QByteArray &result) {
|
||||||
@ -269,8 +298,11 @@ bool FeedsImportExportModel::exportToTxtURLPerLine(QByteArray &result) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data) {
|
void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data, bool fetch_metadata_online) {
|
||||||
emit parsingStarted();
|
emit parsingStarted();
|
||||||
|
emit layoutAboutToBeChanged();
|
||||||
|
setRootItem(NULL);
|
||||||
|
emit layoutChanged();
|
||||||
|
|
||||||
int completed = 0, succeded = 0, failed = 0;
|
int completed = 0, succeded = 0, failed = 0;
|
||||||
StandardServiceRoot *root_item = new StandardServiceRoot();
|
StandardServiceRoot *root_item = new StandardServiceRoot();
|
||||||
@ -278,9 +310,11 @@ void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data) {
|
|||||||
|
|
||||||
foreach (const QByteArray &url, urls) {
|
foreach (const QByteArray &url, urls) {
|
||||||
if (!url.isEmpty()) {
|
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);
|
guessed.first->setUrl(url);
|
||||||
root_item->appendChild(guessed.first);
|
root_item->appendChild(guessed.first);
|
||||||
succeded++;
|
succeded++;
|
||||||
@ -294,7 +328,13 @@ void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray &data) {
|
|||||||
feed->setIcon(qApp->icons()->fromTheme(QSL("folder-feed")));
|
feed->setIcon(qApp->icons()->fromTheme(QSL("folder-feed")));
|
||||||
feed->setEncoding(DEFAULT_FEED_ENCODING);
|
feed->setEncoding(DEFAULT_FEED_ENCODING);
|
||||||
root_item->appendChild(feed);
|
root_item->appendChild(feed);
|
||||||
failed++;
|
|
||||||
|
if (fetch_metadata_online && guessed.second != QNetworkReply::NoError) {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
succeded++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qApp->processEvents();
|
qApp->processEvents();
|
||||||
@ -420,7 +460,14 @@ int FeedsImportExportModel::rowCount(const QModelIndex &parent) const {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return itemForIndex(parent)->childCount();
|
RootItem *item = itemForIndex(parent);
|
||||||
|
|
||||||
|
if (item != NULL) {
|
||||||
|
return item->childCount();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,12 +60,12 @@ class FeedsImportExportModel : public QAbstractItemModel {
|
|||||||
// Exports to OPML 2.0
|
// Exports to OPML 2.0
|
||||||
// NOTE: http://dev.opml.org/spec2.html
|
// NOTE: http://dev.opml.org/spec2.html
|
||||||
bool exportToOMPL20(QByteArray &result);
|
bool exportToOMPL20(QByteArray &result);
|
||||||
void importAsOPML20(const QByteArray &data);
|
void importAsOPML20(const QByteArray &data, bool fetch_metadata_online);
|
||||||
|
|
||||||
// Exports to plain text format
|
// Exports to plain text format
|
||||||
// where there is one feed URL per line.
|
// where there is one feed URL per line.
|
||||||
bool exportToTxtURLPerLine(QByteArray &result);
|
bool exportToTxtURLPerLine(QByteArray &result);
|
||||||
void importAsTxtURLPerLine(const QByteArray &data);
|
void importAsTxtURLPerLine(const QByteArray &data, bool fetch_metadata_online);
|
||||||
|
|
||||||
Mode mode() const;
|
Mode mode() const;
|
||||||
void setMode(const Mode &mode);
|
void setMode(const Mode &mode);
|
||||||
|
@ -84,7 +84,7 @@ void StandardServiceRoot::start(bool freshly_activated) {
|
|||||||
QString output_msg;
|
QString output_msg;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
model.importAsOPML20(IOFactory::readTextFile(file_to_load));
|
model.importAsOPML20(IOFactory::readTextFile(file_to_load), false);
|
||||||
model.checkAllItems();
|
model.checkAllItems();
|
||||||
|
|
||||||
if (mergeImportExportModel(&model, this, output_msg)) {
|
if (mergeImportExportModel(&model, this, output_msg)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user