diff --git a/resources/text/CHANGELOG b/resources/text/CHANGELOG index ed00d2285..a861e5c68 100755 --- a/resources/text/CHANGELOG +++ b/resources/text/CHANGELOG @@ -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) diff --git a/src/services/standard/gui/formstandardimportexport.cpp b/src/services/standard/gui/formstandardimportexport.cpp index daaaf9ff6..873b27992 100755 --- a/src/services/standard/gui/formstandardimportexport.cpp +++ b/src/services/standard/gui/formstandardimportexport.cpp @@ -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; } diff --git a/src/services/standard/gui/formstandardimportexport.h b/src/services/standard/gui/formstandardimportexport.h index 80657edbc..739644cf9 100755 --- a/src/services/standard/gui/formstandardimportexport.h +++ b/src/services/standard/gui/formstandardimportexport.h @@ -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(); diff --git a/src/services/standard/standardfeedsimportexportmodel.cpp b/src/services/standard/standardfeedsimportexportmodel.cpp index a2e322b67..5a7b00f9b 100755 --- a/src/services/standard/standardfeedsimportexportmodel.cpp +++ b/src/services/standard/standardfeedsimportexportmodel.cpp @@ -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 model_items; model_items.push(root_item); QStack 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 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 guessed = StandardFeed::guessFeed(url); + QPair 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; + } } } diff --git a/src/services/standard/standardfeedsimportexportmodel.h b/src/services/standard/standardfeedsimportexportmodel.h index ee9d6eaae..ea81c0021 100755 --- a/src/services/standard/standardfeedsimportexportmodel.h +++ b/src/services/standard/standardfeedsimportexportmodel.h @@ -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); diff --git a/src/services/standard/standardserviceroot.cpp b/src/services/standard/standardserviceroot.cpp index d74f7c13b..3d3559461 100755 --- a/src/services/standard/standardserviceroot.cpp +++ b/src/services/standard/standardserviceroot.cpp @@ -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)) {