Import made better - ability to fetch feed metadata.
This commit is contained in:
		
							parent
							
								
									785b1a344c
								
							
						
					
					
						commit
						55dadfe238
					
				
					 6 changed files with 99 additions and 46 deletions
				
			
		|  | @ -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…
	
	Add table
		
		Reference in a new issue