Import made better - ability to fetch feed metadata.
This commit is contained in:
parent
785b1a344c
commit
55dadfe238
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user