diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index cc707dce6..020343dc8 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -38,9 +38,9 @@ set(SOURCES database/sqlitedriver.cpp database/sqlitedriver.h definitions/definitions.h - definitions/typedefs.h definitions/globals.cpp definitions/globals.h + definitions/typedefs.h dynamic-shortcuts/dynamicshortcuts.cpp dynamic-shortcuts/dynamicshortcuts.h dynamic-shortcuts/dynamicshortcutswidget.cpp @@ -49,20 +49,20 @@ set(SOURCES dynamic-shortcuts/shortcutcatcher.h exceptions/applicationexception.cpp exceptions/applicationexception.h - exceptions/feedrecognizedbutfailedexception.cpp - exceptions/feedrecognizedbutfailedexception.h exceptions/feedfetchexception.cpp exceptions/feedfetchexception.h + exceptions/feedrecognizedbutfailedexception.cpp + exceptions/feedrecognizedbutfailedexception.h exceptions/filteringexception.cpp exceptions/filteringexception.h exceptions/ioexception.cpp exceptions/ioexception.h exceptions/networkexception.cpp exceptions/networkexception.h - exceptions/scriptexception.cpp - exceptions/scriptexception.h exceptions/processexception.cpp exceptions/processexception.h + exceptions/scriptexception.cpp + exceptions/scriptexception.h gui/dialogs/formabout.cpp gui/dialogs/formabout.h gui/dialogs/formaddaccount.cpp @@ -71,10 +71,10 @@ set(SOURCES gui/dialogs/formbackupdatabasesettings.h gui/dialogs/formdatabasecleanup.cpp gui/dialogs/formdatabasecleanup.h - gui/dialogs/formmain.cpp - gui/dialogs/formmain.h gui/dialogs/formlog.cpp gui/dialogs/formlog.h + gui/dialogs/formmain.cpp + gui/dialogs/formmain.h gui/dialogs/formmessagefiltersmanager.cpp gui/dialogs/formmessagefiltersmanager.h gui/dialogs/formrestoredatabasesettings.cpp @@ -97,14 +97,18 @@ set(SOURCES gui/messagepreviewer.h gui/messagesview.cpp gui/messagesview.h + gui/notifications/articlelistnotification.cpp + gui/notifications/articlelistnotification.h gui/notifications/basetoastnotification.cpp gui/notifications/basetoastnotification.h gui/notifications/notificationseditor.cpp gui/notifications/notificationseditor.h gui/notifications/singlenotificationeditor.cpp gui/notifications/singlenotificationeditor.h - gui/notifications/articlelistnotification.cpp - gui/notifications/articlelistnotification.h + gui/notifications/toastnotification.cpp + gui/notifications/toastnotification.h + gui/notifications/toastnotificationsmanager.cpp + gui/notifications/toastnotificationsmanager.h gui/reusable/baselineedit.cpp gui/reusable/baselineedit.h gui/reusable/basetreeview.cpp @@ -113,6 +117,8 @@ set(SOURCES gui/reusable/colortoolbutton.h gui/reusable/comboboxwithstatus.cpp gui/reusable/comboboxwithstatus.h + gui/reusable/discoverfeedsbutton.cpp + gui/reusable/discoverfeedsbutton.h gui/reusable/edittableview.cpp gui/reusable/edittableview.h gui/reusable/helpspoiler.cpp @@ -125,6 +131,8 @@ set(SOURCES gui/reusable/labelwithstatus.h gui/reusable/lineeditwithstatus.cpp gui/reusable/lineeditwithstatus.h + gui/reusable/locationlineedit.cpp + gui/reusable/locationlineedit.h gui/reusable/messagecountspinbox.cpp gui/reusable/messagecountspinbox.h gui/reusable/networkproxydetails.cpp @@ -137,12 +145,12 @@ set(SOURCES gui/reusable/progressbarwithtext.h gui/reusable/resizablestackedwidget.cpp gui/reusable/resizablestackedwidget.h + gui/reusable/searchlineedit.cpp + gui/reusable/searchlineedit.h gui/reusable/searchtextwidget.cpp gui/reusable/searchtextwidget.h gui/reusable/squeezelabel.cpp gui/reusable/squeezelabel.h - gui/reusable/searchlineedit.cpp - gui/reusable/searchlineedit.h gui/reusable/styleditemdelegatewithoutfocus.cpp gui/reusable/styleditemdelegatewithoutfocus.h gui/reusable/timespinbox.cpp @@ -151,6 +159,10 @@ set(SOURCES gui/reusable/treeviewcolumnsmenu.h gui/reusable/widgetwithstatus.cpp gui/reusable/widgetwithstatus.h + gui/richtexteditor/mrichtextedit.cpp + gui/richtexteditor/mrichtextedit.h + gui/richtexteditor/mtextedit.cpp + gui/richtexteditor/mtextedit.h gui/settings/settingsbrowsermail.cpp gui/settings/settingsbrowsermail.h gui/settings/settingsdatabase.cpp @@ -181,15 +193,6 @@ set(SOURCES gui/tabcontent.h gui/tabwidget.cpp gui/tabwidget.h - gui/notifications/toastnotification.cpp - gui/notifications/toastnotification.h - gui/notifications/toastnotificationsmanager.cpp - gui/notifications/toastnotificationsmanager.h - gui/webviewers/webviewer.h - gui/richtexteditor/mrichtextedit.cpp - gui/richtexteditor/mrichtextedit.h - gui/richtexteditor/mtextedit.cpp - gui/richtexteditor/mtextedit.h gui/toolbars/basetoolbar.cpp gui/toolbars/basetoolbar.h gui/toolbars/feedstoolbar.cpp @@ -200,6 +203,9 @@ set(SOURCES gui/toolbars/statusbar.h gui/toolbars/toolbareditor.cpp gui/toolbars/toolbareditor.h + gui/webbrowser.cpp + gui/webbrowser.h + gui/webviewers/webviewer.h miscellaneous/application.cpp miscellaneous/application.h miscellaneous/autosaver.cpp @@ -236,6 +242,14 @@ set(SOURCES miscellaneous/templates.h miscellaneous/textfactory.cpp miscellaneous/textfactory.h + network-web/adblock/adblockdialog.cpp + network-web/adblock/adblockdialog.h + network-web/adblock/adblockicon.cpp + network-web/adblock/adblockicon.h + network-web/adblock/adblockmanager.cpp + network-web/adblock/adblockmanager.h + network-web/adblock/adblockrequestinfo.cpp + network-web/adblock/adblockrequestinfo.h network-web/basenetworkaccessmanager.cpp network-web/basenetworkaccessmanager.h network-web/cookiejar.cpp @@ -244,6 +258,8 @@ set(SOURCES network-web/downloader.h network-web/downloadmanager.cpp network-web/downloadmanager.h + network-web/googlesuggest.cpp + network-web/googlesuggest.h network-web/httpresponse.cpp network-web/httpresponse.h network-web/networkfactory.cpp @@ -268,32 +284,32 @@ set(SOURCES services/abstract/feed.h services/abstract/gui/authenticationdetails.cpp services/abstract/gui/authenticationdetails.h + services/abstract/gui/custommessagepreviewer.cpp + services/abstract/gui/custommessagepreviewer.h services/abstract/gui/formaccountdetails.cpp services/abstract/gui/formaccountdetails.h - services/abstract/gui/formcategorydetails.cpp - services/abstract/gui/formcategorydetails.h - services/abstract/gui/formfeeddetails.cpp - services/abstract/gui/formfeeddetails.h services/abstract/gui/formaddeditlabel.cpp services/abstract/gui/formaddeditlabel.h services/abstract/gui/formaddeditprobe.cpp services/abstract/gui/formaddeditprobe.h - services/abstract/gui/custommessagepreviewer.h - services/abstract/gui/custommessagepreviewer.cpp + services/abstract/gui/formcategorydetails.cpp + services/abstract/gui/formcategorydetails.h + services/abstract/gui/formfeeddetails.cpp + services/abstract/gui/formfeeddetails.h services/abstract/importantnode.cpp services/abstract/importantnode.h services/abstract/label.cpp services/abstract/label.h services/abstract/labelsnode.cpp services/abstract/labelsnode.h - services/abstract/search.cpp - services/abstract/search.h - services/abstract/searchsnode.cpp - services/abstract/searchsnode.h services/abstract/recyclebin.cpp services/abstract/recyclebin.h services/abstract/rootitem.cpp services/abstract/rootitem.h + services/abstract/search.cpp + services/abstract/search.h + services/abstract/searchsnode.cpp + services/abstract/searchsnode.h services/abstract/serviceentrypoint.h services/abstract/serviceroot.cpp services/abstract/serviceroot.h @@ -317,6 +333,8 @@ set(SOURCES services/gmail/gmailnetworkfactory.h services/gmail/gmailserviceroot.cpp services/gmail/gmailserviceroot.h + services/gmail/gui/emailpreviewer.cpp + services/gmail/gui/emailpreviewer.h services/gmail/gui/emailrecipientcontrol.cpp services/gmail/gui/emailrecipientcontrol.h services/gmail/gui/formaddeditemail.cpp @@ -325,8 +343,6 @@ set(SOURCES services/gmail/gui/formeditgmailaccount.h services/gmail/gui/gmailaccountdetails.cpp services/gmail/gui/gmailaccountdetails.h - services/gmail/gui/emailpreviewer.h - services/gmail/gui/emailpreviewer.cpp services/greader/definitions.h services/greader/greaderentrypoint.cpp services/greader/greaderentrypoint.h @@ -367,6 +383,8 @@ set(SOURCES services/reddit/redditsubscription.cpp services/reddit/redditsubscription.h services/standard/definitions.h + services/standard/gui/formdiscoverfeeds.cpp + services/standard/gui/formdiscoverfeeds.h services/standard/gui/formeditstandardaccount.cpp services/standard/gui/formeditstandardaccount.h services/standard/gui/formstandardfeeddetails.cpp @@ -375,10 +393,10 @@ set(SOURCES services/standard/gui/formstandardimportexport.h services/standard/gui/standardfeeddetails.cpp services/standard/gui/standardfeeddetails.h - services/standard/parsers/feedparser.cpp - services/standard/parsers/feedparser.h services/standard/parsers/atomparser.cpp services/standard/parsers/atomparser.h + services/standard/parsers/feedparser.cpp + services/standard/parsers/feedparser.h services/standard/parsers/jsonparser.cpp services/standard/parsers/jsonparser.h services/standard/parsers/rdfparser.cpp @@ -417,22 +435,6 @@ set(SOURCES services/tt-rss/ttrssserviceentrypoint.h services/tt-rss/ttrssserviceroot.cpp services/tt-rss/ttrssserviceroot.h - gui/reusable/discoverfeedsbutton.cpp - gui/reusable/discoverfeedsbutton.h - gui/reusable/locationlineedit.cpp - gui/reusable/locationlineedit.h - gui/webbrowser.cpp - gui/webbrowser.h - network-web/googlesuggest.cpp - network-web/googlesuggest.h - network-web/adblock/adblockdialog.cpp - network-web/adblock/adblockdialog.h - network-web/adblock/adblockicon.cpp - network-web/adblock/adblockicon.h - network-web/adblock/adblockmanager.cpp - network-web/adblock/adblockmanager.h - network-web/adblock/adblockrequestinfo.cpp - network-web/adblock/adblockrequestinfo.h ) set(UI_FILES @@ -440,21 +442,21 @@ set(UI_FILES gui/dialogs/formaddaccount.ui gui/dialogs/formbackupdatabasesettings.ui gui/dialogs/formdatabasecleanup.ui - gui/dialogs/formmain.ui gui/dialogs/formlog.ui + gui/dialogs/formmain.ui gui/dialogs/formmessagefiltersmanager.ui gui/dialogs/formrestoredatabasesettings.ui gui/dialogs/formsettings.ui gui/dialogs/formupdate.ui + gui/itemdetails.ui + gui/newspaperpreviewer.ui + gui/notifications/articlelistnotification.ui gui/notifications/notificationseditor.ui gui/notifications/singlenotificationeditor.ui - gui/notifications/articlelistnotification.ui gui/notifications/toastnotification.ui gui/reusable/networkproxydetails.ui - gui/itemdetails.ui - gui/richtexteditor/mrichtextedit.ui - gui/newspaperpreviewer.ui gui/reusable/searchtextwidget.ui + gui/richtexteditor/mrichtextedit.ui gui/settings/settingsbrowsermail.ui gui/settings/settingsdatabase.ui gui/settings/settingsdownloads.ui @@ -462,44 +464,46 @@ set(UI_FILES gui/settings/settingsgeneral.ui gui/settings/settingsgui.ui gui/settings/settingslocalization.ui - gui/settings/settingsnotifications.ui gui/settings/settingsnodejs.ui + gui/settings/settingsnotifications.ui gui/settings/settingsshortcuts.ui gui/toolbars/toolbareditor.ui + network-web/adblock/adblockdialog.ui network-web/downloaditem.ui network-web/downloadmanager.ui services/abstract/gui/authenticationdetails.ui services/abstract/gui/formaccountdetails.ui - services/abstract/gui/formcategorydetails.ui - services/abstract/gui/formfeeddetails.ui services/abstract/gui/formaddeditlabel.ui services/abstract/gui/formaddeditprobe.ui + services/abstract/gui/formcategorydetails.ui + services/abstract/gui/formfeeddetails.ui services/feedly/gui/feedlyaccountdetails.ui + services/gmail/gui/emailpreviewer.ui services/gmail/gui/formaddeditemail.ui services/gmail/gui/gmailaccountdetails.ui - services/gmail/gui/emailpreviewer.ui services/greader/gui/greaderaccountdetails.ui services/owncloud/gui/owncloudaccountdetails.ui services/reddit/gui/redditaccountdetails.ui + services/standard/gui/formdiscoverfeeds.ui services/standard/gui/formstandardimportexport.ui services/standard/gui/standardfeeddetails.ui services/tt-rss/gui/formttrssnote.ui services/tt-rss/gui/ttrssaccountdetails.ui services/tt-rss/gui/ttrssfeeddetails.ui - network-web/adblock/adblockdialog.ui) +) if(USE_WEBENGINE) list(APPEND SOURCES # WebEngine-based web (and message) browser. - network-web/webengine/webenginepage.cpp - network-web/webengine/webenginepage.h gui/webviewers/webengine/webengineviewer.cpp gui/webviewers/webengine/webengineviewer.h + network-web/adblock/adblockurlinterceptor.cpp + network-web/adblock/adblockurlinterceptor.h network-web/webengine/networkurlinterceptor.cpp network-web/webengine/networkurlinterceptor.h network-web/webengine/urlinterceptor.h - network-web/adblock/adblockurlinterceptor.cpp - network-web/adblock/adblockurlinterceptor.h + network-web/webengine/webenginepage.cpp + network-web/webengine/webenginepage.h ) endif() diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp index 7a4d49d0d..48d1e5e41 100644 --- a/src/librssguard/gui/dialogs/formmain.cpp +++ b/src/librssguard/gui/dialogs/formmain.cpp @@ -32,6 +32,7 @@ #include "services/abstract/recyclebin.h" #include "services/abstract/serviceroot.h" +#include #include #include #include @@ -323,7 +324,7 @@ void FormMain::updateAddItemMenu() { root_menu->addAction(action_new_feed); connect(action_new_feed, &QAction::triggered, activated_root, [activated_root]() { - activated_root->addNewFeed(activated_root); + activated_root->addNewFeed(activated_root, QGuiApplication::clipboard()->text(QClipboard::Mode::Clipboard)); }); } diff --git a/src/librssguard/gui/messagesview.cpp b/src/librssguard/gui/messagesview.cpp index f33b90659..ca143363b 100644 --- a/src/librssguard/gui/messagesview.cpp +++ b/src/librssguard/gui/messagesview.cpp @@ -206,8 +206,8 @@ void MessagesView::copyUrlOfSelectedArticles() const { .toString(); } - if (qApp->clipboard() != nullptr && !urls.isEmpty()) { - qApp->clipboard()->setText(urls.join(TextFactory::newline()), QClipboard::Mode::Clipboard); + if (QGuiApplication::clipboard() != nullptr && !urls.isEmpty()) { + QGuiApplication::clipboard()->setText(urls.join(TextFactory::newline()), QClipboard::Mode::Clipboard); } } diff --git a/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp b/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp new file mode 100644 index 000000000..778f68133 --- /dev/null +++ b/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp @@ -0,0 +1,210 @@ +// For license of this file, see /LICENSE.md. + +#include "services/standard/gui/formdiscoverfeeds.h" + +#include "gui/guiutilities.h" +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "services/abstract/category.h" +#include "services/abstract/serviceroot.h" +#include "services/standard/standardfeed.h" + +#include "services/standard/parsers/atomparser.h" +#include "services/standard/parsers/jsonparser.h" +#include "services/standard/parsers/rdfparser.h" +#include "services/standard/parsers/rssparser.h" +#include "services/standard/parsers/sitemapparser.h" + +#include + +FormDiscoverFeeds::FormDiscoverFeeds(ServiceRoot* service_root, + RootItem* parent_to_select, + const QString& url, + QWidget* parent) + : QDialog(parent), m_serviceRoot(service_root), m_discoveredModel(new DiscoveredFeedsModel(this)) { + m_ui.setupUi(this); + + GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("application-rss+xml"))); + + m_parsers = {new AtomParser({}), new RssParser({}), new RdfParser({}), new JsonParser({}), new SitemapParser({})}; + + m_btnImportSelectedFeeds = + m_ui.m_buttonBox->addButton(tr("Import selected feeds"), QDialogButtonBox::ButtonRole::ActionRole); + + m_btnImportSelectedFeeds->setIcon(qApp->icons()->fromTheme(QSL("document-import"))); + m_ui.m_btnDiscover->setIcon(qApp->icons()->fromTheme(QSL("system-search"))); + + connect(m_ui.m_txtUrl->lineEdit(), &QLineEdit::textChanged, this, &FormDiscoverFeeds::onUrlChanged); + connect(m_btnImportSelectedFeeds, &QPushButton::clicked, this, &FormDiscoverFeeds::importSelectedFeeds); + connect(m_ui.m_btnDiscover, &QPushButton::clicked, this, &FormDiscoverFeeds::discoverFeeds); + + connect(&m_watcherLookup, &QFutureWatcher>::progressValueChanged, this, [=](int prog) { + m_ui.m_pbDiscovery->setValue(prog); + qDebugNN << "progress"; + }); + + connect(&m_watcherLookup, &QFutureWatcher>::finished, this, [=]() { + auto res = m_watcherLookup.future().result(); + + loadDiscoveredFeeds(res); + }); + + loadCategories(m_serviceRoot->getSubTreeCategories(), m_serviceRoot); + + m_ui.m_tvFeeds->setModel(m_discoveredModel); + + m_ui.m_tvFeeds->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeMode::Stretch); + m_ui.m_tvFeeds->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeMode::ResizeToContents); + + m_ui.m_pbDiscovery->setVisible(false); + m_ui.m_txtUrl->lineEdit()->setText(url); + + if (url.isEmpty()) { + emit m_ui.m_txtUrl->lineEdit()->textChanged(url); + } + + m_ui.m_txtUrl->lineEdit()->selectAll(); + m_ui.m_txtUrl->setFocus(); + + if (parent_to_select != nullptr) { + if (parent_to_select->kind() == RootItem::Kind::Category) { + m_ui.m_cmbParentCategory + ->setCurrentIndex(m_ui.m_cmbParentCategory->findData(QVariant::fromValue((void*)parent_to_select))); + } + else if (parent_to_select->kind() == RootItem::Kind::Feed) { + int target_item = m_ui.m_cmbParentCategory->findData(QVariant::fromValue((void*)parent_to_select->parent())); + + if (target_item >= 0) { + m_ui.m_cmbParentCategory->setCurrentIndex(target_item); + } + } + else { + m_ui.m_cmbParentCategory->setCurrentIndex(0); + } + } +} + +FormDiscoverFeeds::~FormDiscoverFeeds() { + qDeleteAll(m_parsers); +} + +void FormDiscoverFeeds::discoverFeeds() { + QString url = m_ui.m_txtUrl->lineEdit()->text(); + + std::function(const FeedParser*)> func = [=](const FeedParser* parser) -> QList { + return parser->discoverFeeds(m_serviceRoot, url); + }; + + std::function(QList&, const QList&)> reducer = + [=](QList& res, const QList& interm) -> QList { + res.append(interm); + return res; + }; + +#if QT_VERSION_MAJOR == 5 + QFuture> fut = QtConcurrent::mappedReduced>(m_parsers, func, reducer); +#else + QFuture> fut = + QtConcurrent::mappedReduced>(qApp->workHorsePool(), m_parsers, func, reducer); +#endif + + m_watcherLookup.setFuture(fut); + + m_ui.m_pbDiscovery->setMaximum(m_parsers.size()); + m_ui.m_pbDiscovery->setValue(0); + m_ui.m_pbDiscovery->setVisible(true); +} + +void FormDiscoverFeeds::onUrlChanged(const QString& new_url) { + if (QUrl(new_url).isValid()) { + m_ui.m_txtUrl->setStatus(WidgetWithStatus::StatusType::Ok, tr("URL is valid.")); + } + else { + m_ui.m_txtUrl->setStatus(WidgetWithStatus::StatusType::Error, tr("URL is NOT valid.")); + } +} + +void FormDiscoverFeeds::loadCategories(const QList& categories, RootItem* root_item) { + m_ui.m_cmbParentCategory->addItem(root_item->fullIcon(), root_item->title(), QVariant::fromValue((void*)root_item)); + + for (Category* category : categories) { + m_ui.m_cmbParentCategory->addItem(category->fullIcon(), category->title(), QVariant::fromValue((void*)category)); + } +} + +void FormDiscoverFeeds::addSingleFeed(StandardFeed* feed) { + /* + QScopedPointer form_pointer(new FormStandardFeedDetails(this, + selected_item, + feed->source(), + qApp->mainFormWidget())); + + form_pointer->addEditFeed(); + */ +} + +void FormDiscoverFeeds::importSelectedFeeds() {} + +void FormDiscoverFeeds::loadDiscoveredFeeds(const QList& feeds) { + m_ui.m_pbDiscovery->setVisible(false); + m_discoveredModel->setDiscoveredFeeds(feeds); + + qDebugNN << "finish"; +} + +DiscoveredFeedsModel::DiscoveredFeedsModel(QObject* parent) : QAbstractListModel(parent) {} + +int DiscoveredFeedsModel::rowCount(const QModelIndex& parent) const { + return m_discoveredFeeds.size(); +} + +int DiscoveredFeedsModel::columnCount(const QModelIndex& parent) const { + return 2; +} + +QVariant DiscoveredFeedsModel::data(const QModelIndex& index, int role) const { + switch (role) { + case Qt::ItemDataRole::DisplayRole: { + if (index.column() == 0) { + return m_discoveredFeeds.at(index.row())->title(); + } + else { + return StandardFeed::typeToString(m_discoveredFeeds.at(index.row())->type()); + } + } + + case Qt::ItemDataRole::DecorationRole: { + if (index.column() == 0) { + return m_discoveredFeeds.at(index.row())->fullIcon(); + } + } + + default: + return {}; + } +} + +QList DiscoveredFeedsModel::discoveredFeeds() const { + return m_discoveredFeeds; +} + +void DiscoveredFeedsModel::setDiscoveredFeeds(const QList& newDiscoveredFeeds) { + m_discoveredFeeds = newDiscoveredFeeds; + + emit layoutAboutToBeChanged(); + emit layoutChanged(); +} + +QVariant DiscoveredFeedsModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Orientation::Vertical) { + return {}; + } + + static QStringList headers = {tr("Title"), tr("Type")}; + + if (role == Qt::ItemDataRole::DisplayRole) { + return headers.at(section); + } + + return {}; +} diff --git a/src/librssguard/services/standard/gui/formdiscoverfeeds.h b/src/librssguard/services/standard/gui/formdiscoverfeeds.h new file mode 100644 index 000000000..2fdb1e98e --- /dev/null +++ b/src/librssguard/services/standard/gui/formdiscoverfeeds.h @@ -0,0 +1,65 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FORMDISCOVERFEEDS_H +#define FORMDISCOVERFEEDS_H + +#include + +#include "ui_formdiscoverfeeds.h" + +#include "services/standard/parsers/feedparser.h" + +#include + +class ServiceRoot; +class RootItem; +class Category; + +class DiscoveredFeedsModel : public QAbstractListModel { + Q_OBJECT + + public: + explicit DiscoveredFeedsModel(QObject* parent = {}); + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual QVariant data(const QModelIndex& index, int role) const; + + QList discoveredFeeds() const; + void setDiscoveredFeeds(const QList& newDiscoveredFeeds); + + private: + QList m_discoveredFeeds; +}; + +class FormDiscoverFeeds : public QDialog { + Q_OBJECT + + public: + explicit FormDiscoverFeeds(ServiceRoot* service_root, + RootItem* parent_to_select = {}, + const QString& url = {}, + QWidget* parent = {}); + virtual ~FormDiscoverFeeds(); + + private slots: + void discoverFeeds(); + void onUrlChanged(const QString& new_url); + void addSingleFeed(StandardFeed* feed); + void importSelectedFeeds(); + + private: + void loadDiscoveredFeeds(const QList& feeds); + void loadCategories(const QList& categories, RootItem* root_item); + + private: + Ui::FormDiscoverFeeds m_ui; + QPushButton* m_btnImportSelectedFeeds; + ServiceRoot* m_serviceRoot; + QList m_parsers; + QFutureWatcher> m_watcherLookup; + DiscoveredFeedsModel* m_discoveredModel; +}; + +#endif // FORMDISCOVERFEEDS_H diff --git a/src/librssguard/services/standard/gui/formdiscoverfeeds.ui b/src/librssguard/services/standard/gui/formdiscoverfeeds.ui new file mode 100644 index 000000000..4d4e3f278 --- /dev/null +++ b/src/librssguard/services/standard/gui/formdiscoverfeeds.ui @@ -0,0 +1,199 @@ + + + FormDiscoverFeeds + + + + 0 + 0 + 406 + 334 + + + + Discover feeds + + + + + + URL + + + m_txtUrl + + + + + + + + + + 1 + 0 + + + + + + + + Discover! + + + + + + + + + + 0 + 1 + + + + Discovered feeds + + + + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + true + + + true + + + false + + + + + + + Target parent folder + + + m_cmbParentCategory + + + + + + + + + Select parent item for your feed. + + + + 12 + 12 + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 16777215 + 5 + + + + false + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + LineEditWithStatus + QWidget +
lineeditwithstatus.h
+ 1 +
+
+ + + + m_buttonBox + accepted() + FormDiscoverFeeds + accept() + + + 248 + 254 + + + 157 + 274 + + + + + m_buttonBox + rejected() + FormDiscoverFeeds + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp b/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp index b9593a5a8..e946027e7 100644 --- a/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp +++ b/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp @@ -75,7 +75,7 @@ void FormStandardFeedDetails::apply() { StandardFeed::Type type = static_cast(m_standardFeedDetails->m_ui.m_cmbType ->itemData(m_standardFeedDetails->m_ui.m_cmbType->currentIndex()) - .value()); + .toInt()); // Setup data for new_feed. std_feed->setTitle(m_standardFeedDetails->m_ui.m_txtTitle->lineEdit()->text().simplified()); diff --git a/src/librssguard/services/standard/gui/standardfeeddetails.cpp b/src/librssguard/services/standard/gui/standardfeeddetails.cpp index 16905d61f..00d787ae4 100644 --- a/src/librssguard/services/standard/gui/standardfeeddetails.cpp +++ b/src/librssguard/services/standard/gui/standardfeeddetails.cpp @@ -12,7 +12,6 @@ #include "services/abstract/category.h" #include "services/standard/definitions.h" -#include #include #include #include @@ -251,30 +250,47 @@ void StandardFeedDetails::onDescriptionChanged(const QString& new_description) { } void StandardFeedDetails::onUrlChanged(const QString& new_url) { - if (sourceType() == StandardFeed::SourceType::Url) { - if (QRegularExpression(QSL(URL_REGEXP)).match(new_url).hasMatch()) { - m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The URL is ok.")); + switch (sourceType()) { + case StandardFeed::SourceType::Url: { + if (QUrl(new_url).isValid()) { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The URL is ok.")); + } + else if (!new_url.simplified().isEmpty()) { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Warning, + tr("The URL does not meet standard pattern. " + "Does your URL start with \"http://\" or \"https://\" prefix.")); + } + else { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("The URL is empty.")); + } + + break; } - else if (!new_url.simplified().isEmpty()) { - m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Warning, - tr("The URL does not meet standard pattern. " - "Does your URL start with \"http://\" or \"https://\" prefix.")); + + case StandardFeed::SourceType::Script: { + try { + TextFactory::tokenizeProcessArguments(new_url); + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("Source is ok.")); + } + catch (const ApplicationException& ex) { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("Error: %1").arg(ex.message())); + } + + break; } - else { - m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("The URL is empty.")); + case StandardFeed::SourceType::LocalFile: { + if (QFile::exists(new_url)) { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("File exists.")); + } + else { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("File does not exist.")); + } + + break; } - } - else if (sourceType() == StandardFeed::SourceType::Script) { - try { - TextFactory::tokenizeProcessArguments(new_url); - m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("Source is ok.")); - } - catch (const ApplicationException& ex) { - m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("Error: %1").arg(ex.message())); - } - } - else { - m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The source is ok.")); + + default: + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The source is ok.")); } } @@ -358,9 +374,9 @@ void StandardFeedDetails::prepareForNewFeed(RootItem* parent_to_select, const QS if (!url.isEmpty()) { m_ui.m_txtSource->textEdit()->setPlainText(url); } - else if (Application::clipboard()->mimeData()->hasText()) { + /*else if (Application::clipboard()->mimeData()->hasText()) { m_ui.m_txtSource->textEdit()->setPlainText(Application::clipboard()->text()); - } + }*/ m_ui.m_txtSource->setFocus(); m_ui.m_txtSource->textEdit()->selectAll(); diff --git a/src/librssguard/services/standard/parsers/atomparser.cpp b/src/librssguard/services/standard/parsers/atomparser.cpp index 0ad57360a..e77602393 100644 --- a/src/librssguard/services/standard/parsers/atomparser.cpp +++ b/src/librssguard/services/standard/parsers/atomparser.cpp @@ -23,6 +23,10 @@ AtomParser::AtomParser(const QString& data) : FeedParser(data) { AtomParser::~AtomParser() {} +QList AtomParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { + return {}; +} + QPair> AtomParser::guessFeed(const QByteArray& content, const QString& content_type) const { QString xml_schema_encoding = QSL(DEFAULT_FEED_ENCODING); diff --git a/src/librssguard/services/standard/parsers/atomparser.h b/src/librssguard/services/standard/parsers/atomparser.h index 94f71668f..2082d35a6 100644 --- a/src/librssguard/services/standard/parsers/atomparser.h +++ b/src/librssguard/services/standard/parsers/atomparser.h @@ -15,6 +15,8 @@ class AtomParser : public FeedParser { explicit AtomParser(const QString& data); virtual ~AtomParser(); + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/parsers/feedparser.cpp b/src/librssguard/services/standard/parsers/feedparser.cpp index 1bb7c1bdd..9d85fd416 100644 --- a/src/librssguard/services/standard/parsers/feedparser.cpp +++ b/src/librssguard/services/standard/parsers/feedparser.cpp @@ -41,7 +41,7 @@ FeedParser::FeedParser(QString data, bool is_xml) FeedParser::~FeedParser() {} -QStringList FeedParser::discoverFeeds(const QUrl& url) const { +QList FeedParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { return {}; } diff --git a/src/librssguard/services/standard/parsers/feedparser.h b/src/librssguard/services/standard/parsers/feedparser.h index f703a37cf..4dcbddf7e 100644 --- a/src/librssguard/services/standard/parsers/feedparser.h +++ b/src/librssguard/services/standard/parsers/feedparser.h @@ -11,8 +11,7 @@ #include "core/message.h" #include "definitions/typedefs.h" - -class StandardFeed; +#include "services/standard/standardfeed.h" // Base class for all XML-based feed parsers. class FeedParser { @@ -21,7 +20,7 @@ class FeedParser { virtual ~FeedParser(); // Returns list of absolute URLs of discovered feeds from provided base URL. - virtual QStringList discoverFeeds(const QUrl& url) const; + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; // Guesses feed. virtual QPair> guessFeed(const QByteArray& content, diff --git a/src/librssguard/services/standard/parsers/jsonparser.cpp b/src/librssguard/services/standard/parsers/jsonparser.cpp index b87e6bf8d..539c0ec6c 100644 --- a/src/librssguard/services/standard/parsers/jsonparser.cpp +++ b/src/librssguard/services/standard/parsers/jsonparser.cpp @@ -18,6 +18,10 @@ JsonParser::JsonParser(const QString& data) : FeedParser(data, false) {} JsonParser::~JsonParser() {} +QList JsonParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { + return {}; +} + QPair> JsonParser::guessFeed(const QByteArray& content, const QString& content_type) const { if (content_type.contains(QSL("json"), Qt::CaseSensitivity::CaseInsensitive) || diff --git a/src/librssguard/services/standard/parsers/jsonparser.h b/src/librssguard/services/standard/parsers/jsonparser.h index 9ec826e3d..5f1c1f95a 100644 --- a/src/librssguard/services/standard/parsers/jsonparser.h +++ b/src/librssguard/services/standard/parsers/jsonparser.h @@ -12,6 +12,8 @@ class JsonParser : public FeedParser { explicit JsonParser(const QString& data); virtual ~JsonParser(); + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/parsers/rdfparser.cpp b/src/librssguard/services/standard/parsers/rdfparser.cpp index 6f97037e1..1adcd9c3a 100644 --- a/src/librssguard/services/standard/parsers/rdfparser.cpp +++ b/src/librssguard/services/standard/parsers/rdfparser.cpp @@ -17,6 +17,10 @@ RdfParser::RdfParser(const QString& data) RdfParser::~RdfParser() {} +QList RdfParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { + return {}; +} + QPair> RdfParser::guessFeed(const QByteArray& content, const QString& content_type) const { QString xml_schema_encoding = QSL(DEFAULT_FEED_ENCODING); diff --git a/src/librssguard/services/standard/parsers/rdfparser.h b/src/librssguard/services/standard/parsers/rdfparser.h index dbdb23aae..d399624b4 100644 --- a/src/librssguard/services/standard/parsers/rdfparser.h +++ b/src/librssguard/services/standard/parsers/rdfparser.h @@ -14,6 +14,8 @@ class RdfParser : public FeedParser { explicit RdfParser(const QString& data); virtual ~RdfParser(); + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/parsers/rssparser.cpp b/src/librssguard/services/standard/parsers/rssparser.cpp index 5d2590388..faa2f7aba 100644 --- a/src/librssguard/services/standard/parsers/rssparser.cpp +++ b/src/librssguard/services/standard/parsers/rssparser.cpp @@ -3,7 +3,10 @@ #include "services/standard/parsers/rssparser.h" #include "exceptions/applicationexception.h" +#include "miscellaneous/application.h" +#include "miscellaneous/settings.h" #include "miscellaneous/textfactory.h" +#include "network-web/networkfactory.h" #include "services/standard/definitions.h" #include "services/standard/standardfeed.h" @@ -15,6 +18,89 @@ RssParser::RssParser(const QString& data) : FeedParser(data) {} RssParser::~RssParser() {} +QList RssParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { + QList feeds; + + // Download URL. + int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); + QByteArray data; + auto res = NetworkFactory::performNetworkOperation(url.toString(), + timeout, + {}, + data, + QNetworkAccessManager::Operation::GetOperation, + {}, + {}, + {}, + {}, + root->networkProxy()); + + if (res.m_networkError == QNetworkReply::NetworkError::NoError) { + // Parse result, might be HTML or directly the feed file. + try { + auto guessed_feed = guessFeed(data, res.m_contentType); + + guessed_feed.first->setSource(url.toString()); + + return {guessed_feed.first}; + } + catch (...) { + qDebugNN << LOGSEC_CORE << QUOTE_W_SPACE(url) << "is not a direct feed file."; + } + + QRegularExpression rx(QSL(FEED_REGEX_MATCHER), QRegularExpression::PatternOption::CaseInsensitiveOption); + QRegularExpression rx_href(QSL(FEED_HREF_REGEX_MATCHER), QRegularExpression::PatternOption::CaseInsensitiveOption); + + rx_href.optimize(); + + QRegularExpressionMatchIterator it_rx = rx.globalMatch(QString::fromUtf8(data)); + + while (it_rx.hasNext()) { + QRegularExpressionMatch mat_tx = it_rx.next(); + QString link_tag = mat_tx.captured(); + QString feed_link = rx_href.match(link_tag).captured(1); + + if (feed_link.startsWith(QL1S("//"))) { + feed_link = QSL(URI_SCHEME_HTTP) + feed_link.mid(2); + } + else if (feed_link.startsWith(QL1C('/'))) { + feed_link = url.toString(QUrl::UrlFormattingOption::RemovePath | QUrl::UrlFormattingOption::RemoveQuery | + QUrl::UrlFormattingOption::StripTrailingSlash) + + feed_link; + } + + QByteArray data; + auto res = NetworkFactory::performNetworkOperation(feed_link, + timeout, + {}, + data, + QNetworkAccessManager::Operation::GetOperation, + {}, + {}, + {}, + {}, + root->networkProxy()); + + if (res.m_networkError == QNetworkReply::NetworkError::NoError) { + // Parse result, might be HTML or directly the feed file. + try { + auto guessed_feed = guessFeed(data, res.m_contentType); + + guessed_feed.first->setSource(url.toString()); + + feeds.append(guessed_feed.first); + } + catch (const ApplicationException& ex) { + qDebugNN << LOGSEC_CORE << QUOTE_W_SPACE(url) + << " should be direct link to feed file but was not recognized:" << QUOTE_W_SPACE_DOT(ex.message()); + } + } + } + } + + return feeds; +} + QPair> RssParser::guessFeed(const QByteArray& content, const QString& content_type) const { QString xml_schema_encoding = QSL(DEFAULT_FEED_ENCODING); diff --git a/src/librssguard/services/standard/parsers/rssparser.h b/src/librssguard/services/standard/parsers/rssparser.h index f8abfb0b0..b1e9e4846 100644 --- a/src/librssguard/services/standard/parsers/rssparser.h +++ b/src/librssguard/services/standard/parsers/rssparser.h @@ -14,6 +14,8 @@ class RssParser : public FeedParser { explicit RssParser(const QString& data); virtual ~RssParser(); + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/parsers/sitemapparser.cpp b/src/librssguard/services/standard/parsers/sitemapparser.cpp index b11fc5a9a..79dbdd3de 100644 --- a/src/librssguard/services/standard/parsers/sitemapparser.cpp +++ b/src/librssguard/services/standard/parsers/sitemapparser.cpp @@ -20,6 +20,10 @@ SitemapParser::SitemapParser(const QString& data) : FeedParser(data) {} SitemapParser::~SitemapParser() {} +QList SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { + return {}; +} + QPair> SitemapParser::guessFeed(const QByteArray& content, const QString& content_type) const { QByteArray uncompressed_content; diff --git a/src/librssguard/services/standard/parsers/sitemapparser.h b/src/librssguard/services/standard/parsers/sitemapparser.h index c0df070a4..d16438706 100644 --- a/src/librssguard/services/standard/parsers/sitemapparser.h +++ b/src/librssguard/services/standard/parsers/sitemapparser.h @@ -12,6 +12,8 @@ class SitemapParser : public FeedParser { explicit SitemapParser(const QString& data); virtual ~SitemapParser(); + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/standardserviceroot.cpp b/src/librssguard/services/standard/standardserviceroot.cpp index a33d12a48..c1754d2f2 100644 --- a/src/librssguard/services/standard/standardserviceroot.cpp +++ b/src/librssguard/services/standard/standardserviceroot.cpp @@ -15,6 +15,7 @@ #include "network-web/networkfactory.h" #include "services/abstract/gui/formcategorydetails.h" #include "services/standard/definitions.h" +#include "services/standard/gui/formdiscoverfeeds.h" #include "services/standard/gui/formeditstandardaccount.h" #include "services/standard/gui/formstandardfeeddetails.h" #include "services/standard/gui/formstandardimportexport.h" @@ -33,7 +34,6 @@ #endif #include -#include #include #include #include @@ -135,12 +135,22 @@ void StandardServiceRoot::addNewFeed(RootItem* selected_item, const QString& url return; } + QScopedPointer form_discover(new FormDiscoverFeeds(this, + selected_item, + url, + qApp->mainFormWidget())); + + form_discover->exec(); + + /* QScopedPointer form_pointer(new FormStandardFeedDetails(this, selected_item, url, qApp->mainFormWidget())); form_pointer->addEditFeed(); + */ + qApp->feedUpdateLock()->unlock(); } diff --git a/src/librssguard/services/tt-rss/gui/formttrssfeeddetails.cpp b/src/librssguard/services/tt-rss/gui/formttrssfeeddetails.cpp index 54bacc0b7..b40b60d6e 100644 --- a/src/librssguard/services/tt-rss/gui/formttrssfeeddetails.cpp +++ b/src/librssguard/services/tt-rss/gui/formttrssfeeddetails.cpp @@ -10,7 +10,6 @@ #include "services/tt-rss/ttrssnetworkfactory.h" #include "services/tt-rss/ttrssserviceroot.h" -#include #include #include @@ -69,9 +68,11 @@ void FormTtRssFeedDetails::loadFeedData() { if (!m_urlToProcess.isEmpty()) { m_feedDetails->ui.m_txtUrl->lineEdit()->setText(m_urlToProcess); } + /* else if (Application::clipboard()->mimeData()->hasText()) { m_feedDetails->ui.m_txtUrl->lineEdit()->setText(Application::clipboard()->text()); } + */ m_feedDetails->ui.m_txtUrl->lineEdit()->selectAll(); m_feedDetails->ui.m_txtUrl->setFocus(); diff --git a/src/librssguard/services/tt-rss/ttrssserviceroot.cpp b/src/librssguard/services/tt-rss/ttrssserviceroot.cpp index 239febc6f..b3f9571b1 100644 --- a/src/librssguard/services/tt-rss/ttrssserviceroot.cpp +++ b/src/librssguard/services/tt-rss/ttrssserviceroot.cpp @@ -19,7 +19,6 @@ #include "services/tt-rss/ttrssnetworkfactory.h" #include "services/tt-rss/ttrssserviceentrypoint.h" -#include #include #include