diff --git a/src/articleviewerwidget.cpp b/src/articleviewerwidget.cpp index 56dd433b..56ee1e17 100644 --- a/src/articleviewerwidget.cpp +++ b/src/articleviewerwidget.cpp @@ -1,421 +1,420 @@ /* This file is part of Akregator. Copyright (C) 2004 Teemu Rytilahti 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "articleviewerwidget.h" #include "akregatorconfig.h" #include "aboutdata.h" #include "actionmanager.h" #include "actions.h" #include "article.h" #include "articleformatter.h" #include "articlejobs.h" #include "articlematcher.h" #include "feed.h" #include "folder.h" #include "treenode.h" #include "utils.h" #include "openurlrequest.h" #include "akregator_debug.h" #include "akregator-version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akregator; using namespace Akregator::Filters; ArticleViewerWidget::ArticleViewerWidget(const QString &grantleeDirectory, KActionCollection *ac, QWidget *parent) : QWidget(parent) - , m_imageDir(QUrl::fromLocalFile(QString(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/akregator/Media/")))) , m_node(nullptr) , m_viewMode(NormalView) , m_articleViewerWidgetNg(new Akregator::ArticleViewerWebEngineWidgetNg(ac, this)) , m_grantleeDirectory(grantleeDirectory) { QGridLayout *layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_articleViewerWidgetNg); m_articleHtmlWriter = new Akregator::ArticleHtmlWebEngineWriter(m_articleViewerWidgetNg->articleViewerNg(), this); connect(m_articleViewerWidgetNg->articleViewerNg(), &ArticleViewerWebEngine::signalOpenUrlRequest, this, &ArticleViewerWidget::signalOpenUrlRequest); connect(m_articleViewerWidgetNg->articleViewerNg(), &ArticleViewerWebEngine::showStatusBarMessage, this, &ArticleViewerWidget::showStatusBarMessage); } ArticleViewerWidget::~ArticleViewerWidget() { } QSharedPointer ArticleViewerWidget::normalViewFormatter() { if (!m_normalViewFormatter.data()) { - m_normalViewFormatter = QSharedPointer(new DefaultNormalViewFormatter(m_grantleeDirectory, m_imageDir, m_articleViewerWidgetNg->articleViewerNg())); + m_normalViewFormatter = QSharedPointer(new DefaultNormalViewFormatter(m_grantleeDirectory, m_articleViewerWidgetNg->articleViewerNg())); } return m_normalViewFormatter; } QSharedPointer ArticleViewerWidget::combinedViewFormatter() { if (!m_combinedViewFormatter.data()) { - m_combinedViewFormatter = QSharedPointer(new DefaultCombinedViewFormatter(m_grantleeDirectory, m_imageDir, m_articleViewerWidgetNg->articleViewerNg())); + m_combinedViewFormatter = QSharedPointer(new DefaultCombinedViewFormatter(m_grantleeDirectory, m_articleViewerWidgetNg->articleViewerNg())); } return m_combinedViewFormatter; } void ArticleViewerWidget::slotZoomChangeInFrame(qreal value) { m_articleViewerWidgetNg->articleViewerNg()->setZoomFactor(value); } void ArticleViewerWidget::slotCopy() { m_articleViewerWidgetNg->articleViewerNg()->slotCopy(); } void ArticleViewerWidget::slotSelectionChanged() { ActionManager::getInstance()->action(QStringLiteral("viewer_copy"))->setEnabled(!m_articleViewerWidgetNg->articleViewerNg()->selectedText().isEmpty()); } void ArticleViewerWidget::slotPrint() { m_articleViewerWidgetNg->slotPrint(); } void ArticleViewerWidget::slotPrintPreview() { m_articleViewerWidgetNg->slotPrintPreview(); } void ArticleViewerWidget::connectToNode(TreeNode *node) { if (node) { if (m_viewMode == CombinedView) { connect(node, &TreeNode::signalChanged, this, &ArticleViewerWidget::slotUpdateCombinedView); connect(node, &TreeNode::signalArticlesAdded, this, &ArticleViewerWidget::slotArticlesAdded); connect(node, &TreeNode::signalArticlesRemoved, this, &ArticleViewerWidget::slotArticlesRemoved); connect(node, &TreeNode::signalArticlesUpdated, this, &ArticleViewerWidget::slotArticlesUpdated); } else if (m_viewMode == SummaryView) { connect(node, &TreeNode::signalChanged, this, &ArticleViewerWidget::slotShowSummary); } connect(node, &TreeNode::signalDestroyed, this, &ArticleViewerWidget::slotClear); } } void ArticleViewerWidget::disconnectFromNode(TreeNode *node) { if (node) { node->disconnect(this); } } void ArticleViewerWidget::renderContent(const QString &text) { m_currentText = text; reload(); } void ArticleViewerWidget::beginWriting() { m_articleHtmlWriter->begin(); } void ArticleViewerWidget::endWriting() { m_articleHtmlWriter->end(); } void ArticleViewerWidget::slotShowSummary(TreeNode *node) { m_viewMode = SummaryView; if (!node) { slotClear(); return; } if (node != m_node) { disconnectFromNode(m_node); connectToNode(node); m_node = node; } const QString summary = normalViewFormatter()->formatSummary(node); m_link.clear(); renderContent(summary); setArticleActionsEnabled(false); } void ArticleViewerWidget::showArticle(const Akregator::Article &article) { if (article.isNull() || article.isDeleted()) { slotClear(); return; } const QUrl xmlUrl = QUrl(article.feed()->xmlUrl()); qCDebug(AKREGATOR_LOG) << "showing Article - xmlUrl:" << xmlUrl; m_articleHtmlWriter->setBaseUrl(xmlUrl); m_viewMode = NormalView; disconnectFromNode(m_node); m_article = article; m_node = nullptr; m_link = article.link(); if (article.feed()->loadLinkedWebsite()) { openUrl(article.link()); } else { renderContent(normalViewFormatter()->formatArticles(QVector() << article, ArticleFormatter::ShowIcon)); } setArticleActionsEnabled(true); } bool ArticleViewerWidget::openUrl(const QUrl &url) { if (!m_article.isNull() && m_article.feed()->loadLinkedWebsite()) { m_articleViewerWidgetNg->articleViewerNg()->load(url); } else { reload(); } return true; } void ArticleViewerWidget::setFilters(const std::vector< QSharedPointer > &filters) { if (filters == m_filters) { return; } m_filters = filters; slotUpdateCombinedView(); } void ArticleViewerWidget::slotUpdateCombinedView() { if (m_viewMode != CombinedView) { return; } if (!m_node) { return slotClear(); } m_articleViewerWidgetNg->saveCurrentPosition(); QString text; int num = 0; QElapsedTimer spent; spent.start(); const std::vector< QSharedPointer >::const_iterator filterEnd = m_filters.cend(); QVector
articles; for (const Article &i : qAsConst(m_articles)) { if (i.isDeleted()) { continue; } auto func = [i](const QSharedPointer &matcher) -> bool { return !matcher->matches(i); }; if (std::find_if(m_filters.cbegin(), filterEnd, func) != filterEnd) { continue; } articles << i; ++num; } text = combinedViewFormatter()->formatArticles(articles, ArticleFormatter::NoIcon); qCDebug(AKREGATOR_LOG) << "Combined view rendering: (" << num << " articles):" << "generating HTML:" << spent.elapsed() << "ms"; renderContent(text); qCDebug(AKREGATOR_LOG) << "HTML rendering:" << spent.elapsed() << "ms"; } void ArticleViewerWidget::slotArticlesUpdated(TreeNode * /*node*/, const QVector
& /*list*/) { if (m_viewMode == CombinedView) { //TODO slotUpdateCombinedView(); } } void ArticleViewerWidget::slotArticlesAdded(TreeNode * /*node*/, const QVector
&list) { if (m_viewMode == CombinedView) { //TODO sort list, then merge m_articles << list; std::sort(m_articles.begin(), m_articles.end()); slotUpdateCombinedView(); } } void ArticleViewerWidget::slotArticlesRemoved(TreeNode * /*node*/, const QVector
&list) { Q_UNUSED(list) if (m_viewMode == CombinedView) { //TODO slotUpdateCombinedView(); } } void ArticleViewerWidget::slotClear() { disconnectFromNode(m_node); m_node = nullptr; m_article = Article(); m_articles.clear(); renderContent(QString()); } void ArticleViewerWidget::showNode(TreeNode *node) { m_viewMode = CombinedView; if (node != m_node) { disconnectFromNode(m_node); } connectToNode(node); m_articles.clear(); m_article = Article(); m_node = node; delete m_listJob; m_listJob = node->createListJob(); connect(m_listJob.data(), &ArticleListJob::finished, this, &ArticleViewerWidget::slotArticlesListed); m_listJob->start(); slotUpdateCombinedView(); } qreal ArticleViewerWidget::zoomFactor() const { return m_articleViewerWidgetNg->articleViewerNg()->zoomFactor(); } void ArticleViewerWidget::slotArticlesListed(KJob *job) { Q_ASSERT(job); Q_ASSERT(job == m_listJob); TreeNode *node = m_listJob->node(); if (job->error() || !node) { if (!node) { qCWarning(AKREGATOR_LOG) << "Node to be listed is already deleted"; } else { qCWarning(AKREGATOR_LOG) << job->errorText(); } slotUpdateCombinedView(); return; } m_articles = m_listJob->articles(); std::sort(m_articles.begin(), m_articles.end()); if (node && !m_articles.isEmpty()) { m_link = m_articles.first().link(); } else { m_link = QUrl(); } slotUpdateCombinedView(); } void ArticleViewerWidget::keyPressEvent(QKeyEvent *e) { e->ignore(); } void ArticleViewerWidget::updateAfterConfigChanged() { switch (m_viewMode) { case NormalView: if (!m_article.isNull()) { renderContent(normalViewFormatter()->formatArticles(QVector() << m_article, ArticleFormatter::ShowIcon)); } break; case CombinedView: slotUpdateCombinedView(); break; case SummaryView: slotShowSummary(m_node); break; } } void ArticleViewerWidget::reload() { beginWriting(); m_articleHtmlWriter->queue(m_currentText); endWriting(); } QSize ArticleViewerWidget::sizeHint() const { // Increase height a bit so that we can (roughly) read 25 lines of text QSize sh = QWidget::sizeHint(); sh.setHeight(qMax(sh.height(), 25 * fontMetrics().height())); return sh; } void ArticleViewerWidget::displayAboutPage() { m_articleViewerWidgetNg->articleViewerNg()->showAboutPage(); } void ArticleViewerWidget::setArticleActionsEnabled(bool enabled) { ActionManager::getInstance()->setArticleActionsEnabled(enabled); } Akregator::ArticleViewerWebEngineWidgetNg *ArticleViewerWidget::articleViewerWidgetNg() const { return m_articleViewerWidgetNg; } diff --git a/src/articleviewerwidget.h b/src/articleviewerwidget.h index c564eecb..a3aeef2a 100644 --- a/src/articleviewerwidget.h +++ b/src/articleviewerwidget.h @@ -1,168 +1,167 @@ /* This file is part of Akregator. Copyright (C) 2004 Teemu Rytilahti This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_ARTICLEVIEWERWIDGET_H #define AKREGATOR_ARTICLEVIEWERWIDGET_H #include "article.h" #include "akregator_export.h" #include #include #include #include #include class KJob; class KActionCollection; namespace Akregator { namespace Filters { class AbstractMatcher; } class ArticleFormatter; class ArticleListJob; class OpenUrlRequest; class TreeNode; class ArticleHtmlWebEngineWriter; class ArticleViewerWebEngineWidgetNg; class AKREGATOR_EXPORT ArticleViewerWidget : public QWidget { Q_OBJECT public: explicit ArticleViewerWidget(const QString &grantleeDirectory, KActionCollection *ac, QWidget *parent); ~ArticleViewerWidget() override; /** Repaints the view. */ void reload(); void displayAboutPage(); void showArticle(const Article &article); /** Shows the articles of the tree node @c node (combined view). * Changes in the node will update the view automatically. * * @param node The node to observe */ void showNode(Akregator::TreeNode *node); QSize sizeHint() const override; qreal zoomFactor() const; Akregator::ArticleViewerWebEngineWidgetNg *articleViewerWidgetNg() const; void updateAfterConfigChanged(); public Q_SLOTS: void slotPrint(); /** Set filters which will be used if the viewer is in combined view mode */ void setFilters(const std::vector< QSharedPointer > &filters); /** Update view if combined view mode is set. Has to be called when * the displayed node gets modified. */ void slotUpdateCombinedView(); /** * Clears the canvas and disconnects from the currently observed node * (if in combined view mode). */ void slotClear(); void slotShowSummary(Akregator::TreeNode *node); void slotPrintPreview(); void slotCopy(); void slotZoomChangeInFrame(qreal value); Q_SIGNALS: /** This gets emitted when url gets clicked */ void signalOpenUrlRequest(Akregator::OpenUrlRequest &); void showStatusBarMessage(const QString &msg); void selectionChanged(); protected: // methods bool openUrl(const QUrl &url); protected Q_SLOTS: void slotSelectionChanged(); void slotArticlesListed(KJob *job); void slotArticlesUpdated(Akregator::TreeNode *node, const QVector &list); void slotArticlesAdded(Akregator::TreeNode *node, const QVector &list); void slotArticlesRemoved(Akregator::TreeNode *node, const QVector &list); // from ArticleViewer private: QSharedPointer combinedViewFormatter(); QSharedPointer normalViewFormatter(); void keyPressEvent(QKeyEvent *e) override; /** renders @c body. Use this method whereever possible. * @param body html to render, without header and footer */ void renderContent(const QString &body); /** Resets the canvas and adds writes the HTML header to it. */ void beginWriting(); /** Finishes writing to the canvas and completes the HTML (by adding closing tags) */ void endWriting(); void connectToNode(TreeNode *node); void disconnectFromNode(TreeNode *node); void setArticleActionsEnabled(bool enabled); private: QString m_currentText; - QUrl m_imageDir; QPointer m_node; QPointer m_listJob; Article m_article; QVector
m_articles; QUrl m_link; std::vector > m_filters; enum ViewMode { NormalView, CombinedView, SummaryView }; ViewMode m_viewMode; Akregator::ArticleHtmlWebEngineWriter *m_articleHtmlWriter = nullptr; Akregator::ArticleViewerWebEngineWidgetNg *m_articleViewerWidgetNg = nullptr; QSharedPointer m_normalViewFormatter; QSharedPointer m_combinedViewFormatter; QString m_grantleeDirectory; }; } // namespace Akregator #endif // AKREGATOR_ARTICLEVIEWER_H diff --git a/src/feed/feed.cpp b/src/feed/feed.cpp index bae25ee7..899e13fd 100644 --- a/src/feed/feed.cpp +++ b/src/feed/feed.cpp @@ -1,985 +1,977 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "feed.h" #include "akregatorconfig.h" #include "article.h" #include "articlejobs.h" #include "feedstorage.h" #include "fetchqueue.h" #include "folder.h" #include "notificationmanager.h" #include "storage.h" #include "treenodevisitor.h" #include "types.h" #include "utils.h" #include "feedretriever.h" #include "job/downloadfeediconjob.h" #include #include #include "akregator_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using Syndication::ItemPtr; using namespace Akregator; template class Container> QVector valuesToVector(const Container &container) { QVector values; values.reserve(container.size()); for (const Value &value : container) { values << value; } return values; } class Q_DECL_HIDDEN Akregator::Feed::Private { Akregator::Feed *const q; public: explicit Private(Backend::Storage *storage, Akregator::Feed *qq); Backend::Storage *m_storage = nullptr; bool m_autoFetch = false; int m_fetchInterval; ArchiveMode m_archiveMode; int m_maxArticleAge; int m_maxArticleNumber; bool m_markImmediatelyAsRead = false; bool m_useNotification = false; bool m_loadLinkedWebsite = false; Syndication::ErrorCode m_fetchErrorCode; int m_fetchTries; bool m_followDiscovery = false; Syndication::Loader *m_loader = nullptr; bool m_articlesLoaded = false; Backend::FeedStorage *m_archive = nullptr; QString m_xmlUrl; QString m_htmlUrl; QString m_description; QString m_comment; QString m_faviconUrl; /** list of feed articles */ QHash articles; /** list of deleted articles. This contains **/ QVector
m_deletedArticles; /** caches guids of deleted articles for notification */ QVector
m_addedArticlesNotify; QVector
m_removedArticlesNotify; QVector
m_updatedArticlesNotify; - QPixmap m_imagePixmap; + QString m_logoUrl; QIcon m_favicon; mutable int m_totalCount; void setTotalCountDirty() const; }; QString Feed::archiveModeToString(ArchiveMode mode) { switch (mode) { case keepAllArticles: return QStringLiteral("keepAllArticles"); case disableArchiving: return QStringLiteral("disableArchiving"); case limitArticleNumber: return QStringLiteral("limitArticleNumber"); case limitArticleAge: return QStringLiteral("limitArticleAge"); default: break; } return QStringLiteral("globalDefault"); } Akregator::Feed *Feed::fromOPML(const QDomElement &e, Backend::Storage *storage) { if (!e.hasAttribute(QStringLiteral("xmlUrl")) && !e.hasAttribute(QStringLiteral("xmlurl")) && !e.hasAttribute(QStringLiteral("xmlURL"))) { return nullptr; } const QString title = e.hasAttribute(QStringLiteral("text")) ? e.attribute(QStringLiteral("text")) : e.attribute(QStringLiteral("title")); QString xmlUrl = e.hasAttribute(QStringLiteral("xmlUrl")) ? e.attribute(QStringLiteral("xmlUrl")) : e.attribute(QStringLiteral("xmlurl")); if (xmlUrl.isEmpty()) { xmlUrl = e.attribute(QStringLiteral("xmlURL")); } bool useCustomFetchInterval = e.attribute(QStringLiteral("useCustomFetchInterval")) == QLatin1String("true"); const QString htmlUrl = e.attribute(QStringLiteral("htmlUrl")); const QString description = e.attribute(QStringLiteral("description")); const int fetchInterval = e.attribute(QStringLiteral("fetchInterval")).toInt(); const ArchiveMode archiveMode = stringToArchiveMode(e.attribute(QStringLiteral("archiveMode"))); const int maxArticleAge = e.attribute(QStringLiteral("maxArticleAge")).toUInt(); const int maxArticleNumber = e.attribute(QStringLiteral("maxArticleNumber")).toUInt(); const bool markImmediatelyAsRead = e.attribute(QStringLiteral("markImmediatelyAsRead")) == QLatin1String("true"); const bool useNotification = e.attribute(QStringLiteral("useNotification")) == QLatin1String("true"); const bool loadLinkedWebsite = e.attribute(QStringLiteral("loadLinkedWebsite")) == QLatin1String("true"); const QString comment = e.attribute(QStringLiteral("comment")); const QString faviconUrl = e.attribute(QStringLiteral("faviconUrl")); const uint id = e.attribute(QStringLiteral("id")).toUInt(); Feed *const feed = new Feed(storage); feed->setTitle(title); feed->setFaviconUrl(faviconUrl); feed->setXmlUrl(xmlUrl); feed->setCustomFetchIntervalEnabled(useCustomFetchInterval); feed->setHtmlUrl(htmlUrl); feed->setId(id); feed->setDescription(description); feed->setArchiveMode(archiveMode); feed->setUseNotification(useNotification); feed->setFetchInterval(fetchInterval); feed->setMaxArticleAge(maxArticleAge); feed->setMaxArticleNumber(maxArticleNumber); feed->setMarkImmediatelyAsRead(markImmediatelyAsRead); feed->setLoadLinkedWebsite(loadLinkedWebsite); feed->setComment(comment); if (!feed->d->m_archive && storage) { // Instead of loading the articles, we use the cache from storage feed->d->m_archive = storage->archiveFor(xmlUrl); feed->d->m_totalCount = feed->d->m_archive->totalCount(); } return feed; } bool Feed::accept(TreeNodeVisitor *visitor) { if (visitor->visitFeed(this)) { return true; } else { return visitor->visitTreeNode(this); } } QVector Feed::folders() const { return QVector(); } QVector Feed::folders() { return QVector(); } QVector Feed::feeds() const { QVector list; list.append(this); return list; } QVector Feed::feeds() { QVector list; list.append(this); return list; } Article Feed::findArticle(const QString &guid) const { return d->articles.value(guid); } QVector
Feed::articles() { if (!d->m_articlesLoaded) { loadArticles(); } return valuesToVector(d->articles); } Backend::Storage *Feed::storage() { return d->m_storage; } void Feed::loadArticles() { if (d->m_articlesLoaded) { return; } if (!d->m_archive && d->m_storage) { d->m_archive = d->m_storage->archiveFor(xmlUrl()); } QStringList list = d->m_archive->articles(); for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { Article mya(*it, this, d->m_archive); d->articles[mya.guid()] = mya; if (mya.isDeleted()) { d->m_deletedArticles.append(mya); } } d->m_articlesLoaded = true; enforceLimitArticleNumber(); recalcUnreadCount(); } void Feed::recalcUnreadCount() { QVector
tarticles = articles(); QVector
::ConstIterator it; QVector
::ConstIterator en = tarticles.constEnd(); int oldUnread = d->m_archive->unread(); int unread = 0; for (it = tarticles.constBegin(); it != en; ++it) { if (!(*it).isDeleted() && (*it).status() != Read) { ++unread; } } if (unread != oldUnread) { d->m_archive->setUnread(unread); nodeModified(); } } Feed::ArchiveMode Feed::stringToArchiveMode(const QString &str) { if (str == QLatin1String("globalDefault")) { return globalDefault; } else if (str == QLatin1String("keepAllArticles")) { return keepAllArticles; } else if (str == QLatin1String("disableArchiving")) { return disableArchiving; } else if (str == QLatin1String("limitArticleNumber")) { return limitArticleNumber; } else if (str == QLatin1String("limitArticleAge")) { return limitArticleAge; } return globalDefault; } Feed::Private::Private(Backend::Storage *storage_, Akregator::Feed *qq) : q(qq) , m_storage(storage_) , m_autoFetch(false) , m_fetchInterval(30) , m_archiveMode(globalDefault) , m_maxArticleAge(60) , m_maxArticleNumber(1000) , m_markImmediatelyAsRead(false) , m_useNotification(false) , m_loadLinkedWebsite(false) , m_fetchErrorCode(Syndication::Success) , m_fetchTries(0) , m_followDiscovery(false) , m_loader(nullptr) , m_articlesLoaded(false) , m_archive(nullptr) , m_totalCount(-1) { Q_ASSERT(q); Q_ASSERT(m_storage); } void Feed::Private::setTotalCountDirty() const { m_totalCount = -1; } Feed::Feed(Backend::Storage *storage) : TreeNode() , d(new Private(storage, this)) { } Feed::~Feed() { slotAbortFetch(); emitSignalDestroyed(); delete d; d = nullptr; } void Feed::loadFavicon(const QString &url, bool downloadFavicon) { Akregator::DownloadFeedIconJob *job = new Akregator::DownloadFeedIconJob(this); job->setFeedIconUrl(url); job->setDownloadFavicon(downloadFavicon); connect(job, &DownloadFeedIconJob::result, this, [job, this](const QString &result) { setFaviconUrl(result); }); if (!job->start()) { qCWarning(AKREGATOR_LOG) << "Impossible to start DownloadFeedIconJob for url: " << url; } } bool Feed::useCustomFetchInterval() const { return d->m_autoFetch; } void Feed::setCustomFetchIntervalEnabled(bool enabled) { d->m_autoFetch = enabled; } int Feed::fetchInterval() const { return d->m_fetchInterval; } void Feed::setFetchInterval(int interval) { d->m_fetchInterval = interval; } int Feed::maxArticleAge() const { return d->m_maxArticleAge; } void Feed::setMaxArticleAge(int maxArticleAge) { d->m_maxArticleAge = maxArticleAge; } int Feed::maxArticleNumber() const { return d->m_maxArticleNumber; } void Feed::setMaxArticleNumber(int maxArticleNumber) { d->m_maxArticleNumber = maxArticleNumber; } bool Feed::markImmediatelyAsRead() const { return d->m_markImmediatelyAsRead; } bool Feed::isFetching() const { return d->m_loader != nullptr; } void Feed::setMarkImmediatelyAsRead(bool enabled) { d->m_markImmediatelyAsRead = enabled; } void Feed::setComment(const QString &comment) { d->m_comment = comment; } QString Feed::comment() const { return d->m_comment; } void Feed::setUseNotification(bool enabled) { d->m_useNotification = enabled; } bool Feed::useNotification() const { return d->m_useNotification; } void Feed::setLoadLinkedWebsite(bool enabled) { d->m_loadLinkedWebsite = enabled; } bool Feed::loadLinkedWebsite() const { return d->m_loadLinkedWebsite; } -QPixmap Feed::image() const +QString Feed::logoUrl() const { - return d->m_imagePixmap; + return d->m_logoUrl; } QString Feed::xmlUrl() const { return d->m_xmlUrl; } void Feed::setXmlUrl(const QString &s) { d->m_xmlUrl = s; if (!Settings::fetchOnStartup()) { QTimer::singleShot(KRandom::random() % 4000, this, &Feed::slotAddFeedIconListener); // TODO: let's give a gui some time to show up before starting the fetch when no fetch on startup is used. replace this with something proper later... } } QString Feed::htmlUrl() const { return d->m_htmlUrl; } void Feed::setHtmlUrl(const QString &s) { d->m_htmlUrl = s; } QString Feed::faviconUrl() const { return d->m_faviconUrl; } void Feed::setFaviconUrl(const QString &url) { d->m_faviconUrl = url; setFavicon(QIcon(url)); } QString Feed::description() const { return d->m_description; } void Feed::setDescription(const QString &s) { d->m_description = s; } bool Feed::fetchErrorOccurred() const { return d->m_fetchErrorCode != Syndication::Success; } Syndication::ErrorCode Feed::fetchErrorCode() const { return d->m_fetchErrorCode; } bool Feed::isArticlesLoaded() const { return d->m_articlesLoaded; } QDomElement Feed::toOPML(QDomElement parent, QDomDocument document) const { QDomElement el = document.createElement(QStringLiteral("outline")); el.setAttribute(QStringLiteral("text"), title()); el.setAttribute(QStringLiteral("title"), title()); el.setAttribute(QStringLiteral("xmlUrl"), d->m_xmlUrl); el.setAttribute(QStringLiteral("htmlUrl"), d->m_htmlUrl); el.setAttribute(QStringLiteral("id"), QString::number(id())); el.setAttribute(QStringLiteral("description"), d->m_description); el.setAttribute(QStringLiteral("useCustomFetchInterval"), (useCustomFetchInterval() ? QStringLiteral("true") : QStringLiteral("false"))); el.setAttribute(QStringLiteral("fetchInterval"), QString::number(fetchInterval())); el.setAttribute(QStringLiteral("archiveMode"), archiveModeToString(d->m_archiveMode)); el.setAttribute(QStringLiteral("maxArticleAge"), d->m_maxArticleAge); el.setAttribute(QStringLiteral("comment"), d->m_comment); el.setAttribute(QStringLiteral("maxArticleNumber"), d->m_maxArticleNumber); if (d->m_markImmediatelyAsRead) { el.setAttribute(QStringLiteral("markImmediatelyAsRead"), QStringLiteral("true")); } if (d->m_useNotification) { el.setAttribute(QStringLiteral("useNotification"), QStringLiteral("true")); } if (d->m_loadLinkedWebsite) { el.setAttribute(QStringLiteral("loadLinkedWebsite"), QStringLiteral("true")); } el.setAttribute(QStringLiteral("faviconUrl"), d->m_faviconUrl); el.setAttribute(QStringLiteral("maxArticleNumber"), d->m_maxArticleNumber); el.setAttribute(QStringLiteral("type"), QStringLiteral("rss")); // despite some additional fields, it is still "rss" OPML el.setAttribute(QStringLiteral("version"), QStringLiteral("RSS")); parent.appendChild(el); return el; } KJob *Feed::createMarkAsReadJob() { ArticleModifyJob *job = new ArticleModifyJob; const auto arts = articles(); for (const Article &i : arts) { const ArticleId aid = { xmlUrl(), i.guid() }; job->setStatus(aid, Read); } return job; } void Feed::slotAddToFetchQueue(FetchQueue *queue, bool intervalFetchOnly) { if (!intervalFetchOnly) { queue->addFeed(this); } else { int interval = -1; if (useCustomFetchInterval()) { interval = fetchInterval() * 60; } else if (Settings::useIntervalFetch()) { interval = Settings::autoFetchInterval() * 60; } uint lastFetch = d->m_archive->lastFetch().toSecsSinceEpoch(); uint now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); if (interval > 0 && (now - lastFetch) >= static_cast(interval)) { queue->addFeed(this); } } } void Feed::slotAddFeedIconListener() { if (d->m_faviconUrl.isEmpty()) { loadFavicon(d->m_xmlUrl, true); } else { loadFavicon(d->m_faviconUrl, false); } } void Feed::appendArticles(const Syndication::FeedPtr &feed) { d->setTotalCountDirty(); bool changed = false; const bool notify = useNotification() || Settings::useNotifications(); QList items = feed->items(); QList::ConstIterator it = items.constBegin(); QList::ConstIterator en = items.constEnd(); int nudge = 0; QVector
deletedArticles = d->m_deletedArticles; for (; it != en; ++it) { if (!d->articles.contains((*it)->id())) { // article not in list Article mya(*it, this); mya.offsetPubDate(nudge); nudge--; appendArticle(mya); d->m_addedArticlesNotify.append(mya); if (!mya.isDeleted() && !markImmediatelyAsRead()) { mya.setStatus(New); } else { mya.setStatus(Read); } if (notify) { NotificationManager::self()->slotNotifyArticle(mya); } changed = true; } else { // article is in list // if the article's guid is no hash but an ID, we have to check if the article was updated. That's done by comparing the hash values. Article old = d->articles[(*it)->id()]; Article mya(*it, this); if (!mya.guidIsHash() && mya.hash() != old.hash() && !old.isDeleted()) { mya.setKeep(old.keep()); int oldstatus = old.status(); old.setStatus(Read); d->articles.remove(old.guid()); appendArticle(mya); mya.setStatus(oldstatus); d->m_updatedArticlesNotify.append(mya); changed = true; } else if (old.isDeleted()) { deletedArticles.removeAll(mya); } } } QVector
::ConstIterator dit = deletedArticles.constBegin(); QVector
::ConstIterator dtmp; QVector
::ConstIterator den = deletedArticles.constEnd(); // delete articles with delete flag set completely from archive, which aren't in the current feed source anymore while (dit != den) { dtmp = dit; ++dit; d->articles.remove((*dtmp).guid()); d->m_archive->deleteArticle((*dtmp).guid()); d->m_removedArticlesNotify.append(*dtmp); changed = true; d->m_deletedArticles.removeAll(*dtmp); } if (changed) { articlesModified(); } } bool Feed::usesExpiryByAge() const { return (d->m_archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) || d->m_archiveMode == limitArticleAge; } bool Feed::isExpired(const Article &a) const { QDateTime now = QDateTime::currentDateTime(); int expiryAge = -1; // check whether the feed uses the global default and the default is limitArticleAge if (d->m_archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleAge) { expiryAge = Settings::maxArticleAge() * 24 * 3600; } else // otherwise check if this feed has limitArticleAge set if (d->m_archiveMode == limitArticleAge) { expiryAge = d->m_maxArticleAge * 24 * 3600; } return expiryAge != -1 && a.pubDate().secsTo(now) > expiryAge; } void Feed::appendArticle(const Article &a) { if ((a.keep() && Settings::doNotExpireImportantArticles()) || (!usesExpiryByAge() || !isExpired(a))) { // if not expired if (!d->articles.contains(a.guid())) { d->articles[a.guid()] = a; if (!a.isDeleted() && a.status() != Read) { setUnread(unread() + 1); } } } } void Feed::fetch(bool followDiscovery) { d->m_followDiscovery = followDiscovery; d->m_fetchTries = 0; // mark all new as unread for (auto it = d->articles.begin(), end = d->articles.end(); it != end; ++it) { if ((*it).status() == New) { (*it).setStatus(Unread); } } Q_EMIT fetchStarted(this); tryFetch(); } void Feed::slotAbortFetch() { if (d->m_loader) { d->m_loader->abort(); } } void Feed::tryFetch() { d->m_fetchErrorCode = Syndication::Success; d->m_loader = Syndication::Loader::create(this, SLOT(fetchCompleted(Syndication::Loader*, Syndication::FeedPtr, Syndication::ErrorCode))); d->m_loader->loadFrom(QUrl(d->m_xmlUrl), new FeedRetriever()); } void Feed::fetchCompleted(Syndication::Loader *l, Syndication::FeedPtr doc, Syndication::ErrorCode status) { // Note that loader instances delete themselves d->m_loader = nullptr; // fetching wasn't successful: if (status != Syndication::Success) { if (status == Syndication::Aborted) { d->m_fetchErrorCode = Syndication::Success; Q_EMIT fetchAborted(this); } else if (d->m_followDiscovery && (status == Syndication::InvalidXml) && (d->m_fetchTries < 3) && (l->discoveredFeedURL().isValid())) { d->m_fetchTries++; d->m_xmlUrl = l->discoveredFeedURL().url(); Q_EMIT fetchDiscovery(this); tryFetch(); } else { d->m_fetchErrorCode = status; Q_EMIT fetchError(this); } markAsFetchedNow(); return; } loadArticles(); // TODO: make me fly: make this delayed #if SYNDICATION_VERSION >= QT_VERSION_CHECK(5,65,0) if (!doc->icon().isNull() && !doc->icon()->url().isEmpty()) { loadFavicon(doc->icon()->url(), false); } else { loadFavicon(xmlUrl(), true); } #else loadFavicon(xmlUrl(), true); #endif d->m_fetchErrorCode = Syndication::Success; - if (d->m_imagePixmap.isNull()) { - const QString imageFileName = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/akregator/Media/") + Utils::fileNameForUrl(d->m_xmlUrl) + QLatin1String(".png"); - d->m_imagePixmap = QPixmap(imageFileName, "PNG"); + if (!doc->image().isNull()) { + d->m_logoUrl = doc->image()->url(); } if (title().isEmpty()) { setTitle(Syndication::htmlToPlainText(doc->title())); } d->m_description = doc->description(); d->m_htmlUrl = doc->link(); appendArticles(doc); markAsFetchedNow(); Q_EMIT fetched(this); } void Feed::markAsFetchedNow() { if (d->m_archive) { d->m_archive->setLastFetch(QDateTime::currentDateTimeUtc()); } } QIcon Feed::icon() const { if (fetchErrorOccurred()) { return QIcon::fromTheme(QStringLiteral("dialog-error")); } return !d->m_favicon.isNull() ? d->m_favicon : QIcon::fromTheme(QStringLiteral("text-html")); } void Feed::deleteExpiredArticles(ArticleDeleteJob *deleteJob) { if (!usesExpiryByAge()) { return; } setNotificationMode(false); QList toDelete; const QString feedUrl = xmlUrl(); const bool useKeep = Settings::doNotExpireImportantArticles(); for (const Article &i : qAsConst(d->articles)) { if ((!useKeep || !i.keep()) && isExpired(i)) { const ArticleId aid = { feedUrl, i.guid() }; toDelete.append(aid); } } deleteJob->appendArticleIds(toDelete); setNotificationMode(true); } void Feed::setFavicon(const QIcon &icon) { d->m_favicon = icon; nodeModified(); } -void Feed::setImage(const QPixmap &p) +void Feed::setLogoUrl(const QString &url) { - if (p.isNull()) { - return; - } - d->m_imagePixmap = p; - const QString filename = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/akregator/Media/") + Utils::fileNameForUrl(d->m_xmlUrl) + QLatin1String(".png"); - QFileInfo fileInfo(filename); - QDir().mkpath(fileInfo.absolutePath()); - d->m_imagePixmap.save(filename, "PNG"); + d->m_logoUrl = url; nodeModified(); } Feed::ArchiveMode Feed::archiveMode() const { return d->m_archiveMode; } void Feed::setArchiveMode(ArchiveMode archiveMode) { d->m_archiveMode = archiveMode; } int Feed::unread() const { return d->m_archive ? d->m_archive->unread() : 0; } void Feed::setUnread(int unread) { if (d->m_archive && unread != d->m_archive->unread()) { d->m_archive->setUnread(unread); nodeModified(); } } void Feed::setArticleDeleted(Article &a) { d->setTotalCountDirty(); if (!d->m_deletedArticles.contains(a)) { d->m_deletedArticles.append(a); } d->m_updatedArticlesNotify.append(a); articlesModified(); } void Feed::setArticleChanged(Article &a, int oldStatus, bool process) { int newStatus = a.status(); if (oldStatus != -1) { if (oldStatus == Read && newStatus != Read) { setUnread(unread() + 1); } else if (oldStatus != Read && newStatus == Read) { setUnread(unread() - 1); } } d->m_updatedArticlesNotify.append(a); if (process) { articlesModified(); } } int Feed::totalCount() const { if (d->m_totalCount == -1) { d->m_totalCount = std::count_if(d->articles.constBegin(), d->articles.constEnd(), [](const Article &art) -> bool { return !art.isDeleted(); }); } return d->m_totalCount; } TreeNode *Feed::next() { if (nextSibling()) { return nextSibling(); } Folder *p = parent(); while (p) { if (p->nextSibling()) { return p->nextSibling(); } else { p = p->parent(); } } return nullptr; } const TreeNode *Feed::next() const { if (nextSibling()) { return nextSibling(); } const Folder *p = parent(); while (p) { if (p->nextSibling()) { return p->nextSibling(); } else { p = p->parent(); } } return nullptr; } void Feed::doArticleNotification() { if (!d->m_addedArticlesNotify.isEmpty()) { // copy list, otherwise the refcounting in Article::Private breaks for // some reason (causing segfaults) QVector
l = d->m_addedArticlesNotify; Q_EMIT signalArticlesAdded(this, l); d->m_addedArticlesNotify.clear(); } if (!d->m_updatedArticlesNotify.isEmpty()) { // copy list, otherwise the refcounting in Article::Private breaks for // some reason (causing segfaults) QVector
l = d->m_updatedArticlesNotify; Q_EMIT signalArticlesUpdated(this, l); d->m_updatedArticlesNotify.clear(); } if (!d->m_removedArticlesNotify.isEmpty()) { // copy list, otherwise the refcounting in Article::Private breaks for // some reason (causing segfaults) QVector
l = d->m_removedArticlesNotify; Q_EMIT signalArticlesRemoved(this, l); d->m_removedArticlesNotify.clear(); } TreeNode::doArticleNotification(); } void Feed::enforceLimitArticleNumber() { int limit = -1; if (d->m_archiveMode == globalDefault && Settings::archiveMode() == Settings::EnumArchiveMode::limitArticleNumber) { limit = Settings::maxArticleNumber(); } else if (d->m_archiveMode == limitArticleNumber) { limit = maxArticleNumber(); } if (limit == -1 || limit >= d->articles.count() - d->m_deletedArticles.count()) { return; } QVector
articles = valuesToVector(d->articles); std::sort(articles.begin(), articles.end()); int c = 0; const bool useKeep = Settings::doNotExpireImportantArticles(); for (Article i : qAsConst(articles)) { if (c < limit) { if (!i.isDeleted() && (!useKeep || !i.keep())) { ++c; } } else if (!useKeep || !i.keep()) { i.setDeleted(); } } } diff --git a/src/feed/feed.h b/src/feed/feed.h index 5c29886f..b79313d5 100644 --- a/src/feed/feed.h +++ b/src/feed/feed.h @@ -1,308 +1,308 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_FEED_H #define AKREGATOR_FEED_H #include "akregator_export.h" #include "treenode.h" #include #include class QDomElement; class QString; namespace Akregator { class Article; class FetchQueue; class TreeNodeVisitor; class ArticleDeleteJob; namespace Backend { class Storage; } /** represents a feed */ class AKREGATOR_EXPORT Feed : public TreeNode { friend class ::Akregator::Article; friend class ::Akregator::Folder; Q_OBJECT public: /** the archiving modes */ enum ArchiveMode { globalDefault, /**< use default from Settings (default) */ keepAllArticles, /**< Don't delete any articles */ disableArchiving, /**< Don't save any articles except articles with keep flag set (equal to maxArticleNumber() == 0) */ limitArticleNumber, /**< Save maxArticleNumber() articles, plus the ones with keep flag set */ limitArticleAge /**< Save articles not older than maxArticleAge() (or keep flag set) */ }; // class methods /** converts strings to ArchiveMode value if parsing fails, it returns ArchiveMode::globalDefault */ static ArchiveMode stringToArchiveMode(const QString &str); /** converts ArchiveMode values to corresponding strings */ static QString archiveModeToString(ArchiveMode mode); /** creates a Feed object from a description in OPML format */ static Feed *fromOPML(const QDomElement &e, Akregator::Backend::Storage *storage); /** default constructor */ explicit Feed(Akregator::Backend::Storage *storage); ~Feed(); bool accept(TreeNodeVisitor *visitor) override; /** exports the feed settings to OPML */ QDomElement toOPML(QDomElement parent, QDomDocument document) const override; /** returns whether this feed uses its own fetch interval or the global setting @return @c true iff this feed has a custom fetch interval */ bool useCustomFetchInterval() const; /** set if the feed has its custom fetch interval or uses the global setting @param enabled @c true: use custom interval, @c false: use global default */ void setCustomFetchIntervalEnabled(bool enabled); // FIXME is it -1 or 0 to disable interval fetching? /** Returns custom auto fetch interval of this feed. @return custom fetch interval in minutes, 0 if disabled */ int fetchInterval() const; /** Sets custom auto fetch interval. @param interval interval in minutes, -1 for disabling auto fetching */ void setFetchInterval(int interval); /** returns the archiving mode which is used for this feed */ ArchiveMode archiveMode() const; /** sets the archiving mode for this feed */ void setArchiveMode(ArchiveMode archiveMode); /** returns the maximum age of articles used for expiration by age (used in @c limitArticleAge archive mode) @return expiry age in days */ int maxArticleAge() const; /** sets the maximum age of articles used for expiration by age (used in @c limitArticleAge archive mode) @param maxArticleAge expiry age in days */ void setMaxArticleAge(int maxArticleAge); /** returns the article count limit used in @c limitArticleNumber archive mode **/ int maxArticleNumber() const; /** sets the article count limit used in @c limitArticleNumber archive mode **/ void setMaxArticleNumber(int maxArticleNumber); /** if @c true, new articles are marked immediately as read instead of new/unread. Useful for high-traffic feeds. */ bool markImmediatelyAsRead() const; void setMarkImmediatelyAsRead(bool enabled); void setUseNotification(bool enabled); bool useNotification() const; /** if true, the linked URL is loaded directly in the article viewer instead of showing the description */ void setLoadLinkedWebsite(bool enabled); bool loadLinkedWebsite() const; - /** returns the feed image */ - QPixmap image() const; + /** returns the feed logo */ + QString logoUrl() const; /** sets the feed image */ - void setImage(const QPixmap &p); + void setLogoUrl(const QString &url); /** returns the url of the actual feed source (rss/rdf/atom file) */ QString xmlUrl() const; /** sets the url of the actual feed source (rss/rdf/atom file) */ void setXmlUrl(const QString &s); /** returns the URL of the HTML page of this feed */ QString htmlUrl() const; /** sets the URL of the HTML page of this feed */ void setHtmlUrl(const QString &s); Q_REQUIRED_RESULT QString faviconUrl() const; void setFaviconUrl(const QString &url); /** returns the description of this feed */ QString description() const; /** sets the description of this feed */ void setDescription(const QString &s); /** returns article by guid * @param guid the guid of the article to be returned * @return the article object with the given guid, or a * null article if non-existent */ Article findArticle(const QString &guid) const; /** returns whether a fetch error has occurred */ bool fetchErrorOccurred() const; Syndication::ErrorCode fetchErrorCode() const; /** returns the unread count for this feed */ int unread() const override; /** returns the number of total articles in this feed @return number of articles */ int totalCount() const override; /** returns if the article archive of this feed is loaded */ bool isArticlesLoaded() const; /** returns if this node is a feed group (@c false here) */ bool isGroup() const override { return false; } //impl bool isAggregation() const override { return false; } /** returns the next node in the tree. Calling next() unless it returns 0 iterates through the tree in pre-order */ const TreeNode *next() const override; TreeNode *next() override; //impl QIcon icon() const override; /** deletes expired articles */ void deleteExpiredArticles(Akregator::ArticleDeleteJob *job); bool isFetching() const; QVector feeds() const override; QVector feeds() override; QVector folders() const override; QVector folders() override; KJob *createMarkAsReadJob() override; QString comment() const; void setComment(const QString &comment); public Q_SLOTS: /** starts fetching */ void fetch(bool followDiscovery = false); void slotAbortFetch(); /** add this feed to the fetch queue @c queue */ void slotAddToFetchQueue(Akregator::FetchQueue *queue, bool intervalFetchOnly = false) override; void slotAddFeedIconListener(); Q_SIGNALS: /** emitted when fetching started */ void fetchStarted(Akregator::Feed *); /** emitted when feed finished fetching */ void fetched(Akregator::Feed *); /** emitted when a fetch error occurred */ void fetchError(Akregator::Feed *); /** emitted when a feed URL was found by auto discovery */ void fetchDiscovery(Akregator::Feed *); /** emitted when a fetch is aborted */ void fetchAborted(Akregator::Feed *); private: Akregator::Backend::Storage *storage(); private: void setFavicon(const QIcon &icon); void loadFavicon(const QString &url, bool downloadFavicon); QVector
articles() override; /** loads articles from archive **/ void loadArticles(); void enforceLimitArticleNumber(); void recalcUnreadCount(); void doArticleNotification() override; /** sets the unread count for this feed */ void setUnread(int unread); /** notifies that article @c mya was set to "deleted". To be called by @ref Article */ void setArticleDeleted(Article &a); /** Notifies that article @p a was changed. @param oldStatus The old status if the status was changed, or -1 if the status was not changed @param process Set to @c false to disable processing the change (updating article list and updating on-screen unread count) To be called by @ref Article */ void setArticleChanged(Article &a, int oldStatus = -1, bool process = true); void appendArticles(const Syndication::FeedPtr &feed); /** appends article @c a to the article list */ void appendArticle(const Article &a); /** checks whether article @c a is expired (considering custom and global archive mode settings) */ bool isExpired(const Article &a) const; /** returns @c true if either this article uses @c limitArticleAge as custom setting or uses the global default, which is @c limitArticleAge */ bool usesExpiryByAge() const; /** executes the actual fetch action */ void tryFetch(); void markAsFetchedNow(); private Q_SLOTS: void fetchCompleted(Syndication::Loader *loader, Syndication::FeedPtr doc, Syndication::ErrorCode errorCode); private: class Private; Private *d; }; } // namespace Akregator #endif // AKREGATOR_FEED_H diff --git a/src/formatter/articlegrantleeobject.cpp b/src/formatter/articlegrantleeobject.cpp index e1f78d36..f6d7e0b2 100644 --- a/src/formatter/articlegrantleeobject.cpp +++ b/src/formatter/articlegrantleeobject.cpp @@ -1,125 +1,124 @@ /* Copyright (C) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "articlegrantleeobject.h" #include "articleformatter.h" #include "grantleeutil.h" #include "utils.h" #include "feed.h" #include #include #include #include #include using namespace Akregator; -ArticleGrantleeObject::ArticleGrantleeObject(const QUrl &imageDir, const Article &article, ArticleFormatter::IconOption iconOption, QObject *parent) +ArticleGrantleeObject::ArticleGrantleeObject(const Article &article, ArticleFormatter::IconOption iconOption, QObject *parent) : QObject(parent) , mArticle(article) , mArticleFormatOption(iconOption) - , mImageDir(imageDir) { } ArticleGrantleeObject::~ArticleGrantleeObject() { } ArticleGrantleeObject::ArticleStatus ArticleGrantleeObject::articleStatus() const { if (mArticle.status() == ArticleStatus::Read) { return ArticleGrantleeObject::Read; } else if (mArticle.status() == ArticleStatus::Unread) { return ArticleGrantleeObject::Unread; } else { return ArticleGrantleeObject::New; } } QString ArticleGrantleeObject::strippedTitle() const { return Akregator::Utils::stripTags(mArticle.title()); } QString ArticleGrantleeObject::author() const { return mArticle.authorAsHtml(); } QString ArticleGrantleeObject::content() const { return mArticle.content(Article::DescriptionAsFallback); } QString ArticleGrantleeObject::articleLinkUrl() const { return mArticle.link().url(); } QString ArticleGrantleeObject::articlePubDate() const { if (mArticle.pubDate().isValid()) { return QLocale().toString(mArticle.pubDate(), QLocale::ShortFormat); } return {}; } QString ArticleGrantleeObject::enclosure() const { const QString enc = ArticleFormatter::formatEnclosure(*mArticle.enclosure()); return enc; } QString ArticleGrantleeObject::articleCompleteStoryLink() const { QString link; if (mArticle.link().isValid() || (mArticle.guidIsPermaLink() && QUrl(mArticle.guid()).isValid())) { // in case link isn't valid, fall back to the guid permaLink. if (mArticle.link().isValid()) { link = mArticle.link().url(); } else { link = mArticle.guid(); } } return link; } QString ArticleGrantleeObject::imageFeed() const { QString text; - if (mArticleFormatOption == ArticleFormatter::ShowIcon && mArticle.feed() && !mArticle.feed()->image().isNull()) { + if (mArticleFormatOption == ArticleFormatter::ShowIcon && mArticle.feed() && !mArticle.feed()->logoUrl().isEmpty()) { const Feed *feed = mArticle.feed(); - text += GrantleeUtil::imageFeed(feed, mImageDir); + text += GrantleeUtil::imageFeed(feed); } return text; } QString ArticleGrantleeObject::actionToken() const { QUrlQuery query; query.addQueryItem(QStringLiteral("id"), mArticle.guid()); query.addQueryItem(QStringLiteral("feed"), mArticle.feed()->xmlUrl()); return QLatin1Char('?') + query.toString(QUrl::FullyEncoded); } bool ArticleGrantleeObject::important() const { return mArticle.keep(); } diff --git a/src/formatter/articlegrantleeobject.h b/src/formatter/articlegrantleeobject.h index e05903d4..7f372343 100644 --- a/src/formatter/articlegrantleeobject.h +++ b/src/formatter/articlegrantleeobject.h @@ -1,82 +1,81 @@ /* Copyright (C) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ARTICLEGRANTLEEOBJECT_H #define ARTICLEGRANTLEEOBJECT_H #include #include #include "articleformatter.h" #include namespace Akregator { class ArticleGrantleeObject : public QObject { Q_OBJECT Q_PROPERTY(QString strippedTitle READ strippedTitle) Q_PROPERTY(QString author READ author) Q_PROPERTY(QString content READ content) Q_PROPERTY(QString articleLinkUrl READ articleLinkUrl) Q_PROPERTY(QString articlePubDate READ articlePubDate) Q_PROPERTY(QString enclosure READ enclosure) Q_PROPERTY(QString articleCompleteStoryLink READ articleCompleteStoryLink) Q_PROPERTY(QString imageFeed READ imageFeed) Q_PROPERTY(Akregator::ArticleGrantleeObject::ArticleStatus articleStatus READ articleStatus) Q_PROPERTY(bool important READ important) Q_PROPERTY(QString actionToken READ actionToken) public: - explicit ArticleGrantleeObject(const QUrl &imageDir, const Article &article, ArticleFormatter::IconOption icon, QObject *parent = nullptr); + explicit ArticleGrantleeObject(const Article &article, ArticleFormatter::IconOption icon, QObject *parent = nullptr); ~ArticleGrantleeObject(); enum ArticleStatus { Unread = 0, Read, New }; Q_ENUMS(ArticleStatus) Akregator::ArticleGrantleeObject::ArticleStatus articleStatus() const; QString strippedTitle() const; QString author() const; QString content() const; QString articleLinkUrl() const; QString articlePubDate() const; QString enclosure() const; QString articleCompleteStoryLink() const; QString imageFeed() const; //Action QString deleteAction() const; QString markAsReadAction() const; QString markAsUnreadAction() const; QString markAsImportantAction() const; QString sendUrlAction() const; QString sendFileAction() const; bool important() const; QString actionToken() const; private: Article mArticle; ArticleFormatter::IconOption mArticleFormatOption; - QUrl mImageDir; }; } #endif // ARTICLEGRANTLEEOBJECT_H diff --git a/src/formatter/defaultcombinedviewformatter.cpp b/src/formatter/defaultcombinedviewformatter.cpp index 331156e9..499d0788 100644 --- a/src/formatter/defaultcombinedviewformatter.cpp +++ b/src/formatter/defaultcombinedviewformatter.cpp @@ -1,57 +1,57 @@ /* Copyright (C) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "defaultcombinedviewformatter.h" #include "grantleeviewformatter.h" #include "akregatorconfig.h" #include "akregator_debug.h" #include "article.h" #include "feed.h" #include "folder.h" #include "treenode.h" #include "treenodevisitor.h" #include "utils.h" #include #include using namespace Akregator; -DefaultCombinedViewFormatter::DefaultCombinedViewFormatter(const QString &grantleeDirectory, const QUrl &imageDir, QPaintDevice *device) +DefaultCombinedViewFormatter::DefaultCombinedViewFormatter(const QString &grantleeDirectory, QPaintDevice *device) : ArticleFormatter() { const QString combinedPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("akregator/grantleetheme/%1/").arg(grantleeDirectory), QStandardPaths::LocateDirectory); - mGrantleeViewFormatter = new GrantleeViewFormatter(QStringLiteral("combinedview.html"), combinedPath, imageDir, device->logicalDpiY()); + mGrantleeViewFormatter = new GrantleeViewFormatter(QStringLiteral("combinedview.html"), combinedPath, device->logicalDpiY()); } DefaultCombinedViewFormatter::~DefaultCombinedViewFormatter() { delete mGrantleeViewFormatter; } QString DefaultCombinedViewFormatter::formatArticles(const QVector
&articles, IconOption icon) const { return mGrantleeViewFormatter->formatArticles(articles, icon); } QString DefaultCombinedViewFormatter::formatSummary(TreeNode *) const { return QString(); } diff --git a/src/formatter/defaultcombinedviewformatter.h b/src/formatter/defaultcombinedviewformatter.h index 227ef49b..6fb97821 100644 --- a/src/formatter/defaultcombinedviewformatter.h +++ b/src/formatter/defaultcombinedviewformatter.h @@ -1,43 +1,43 @@ /* Copyright (C) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DEFAULTCOMBINEDVIEWFORMATTER_H #define DEFAULTCOMBINEDVIEWFORMATTER_H #include "articleformatter.h" #include "akregator_export.h" class QPaintDevice; namespace Akregator { class GrantleeViewFormatter; class AKREGATOR_EXPORT DefaultCombinedViewFormatter : public ArticleFormatter { public: - explicit DefaultCombinedViewFormatter(const QString &grantleeDirectory, const QUrl &imageDir, QPaintDevice *device = nullptr); + explicit DefaultCombinedViewFormatter(const QString &grantleeDirectory, QPaintDevice *device = nullptr); ~DefaultCombinedViewFormatter() override; QString formatArticles(const QVector
&articles, IconOption option) const override; QString formatSummary(TreeNode *node) const override; private: DefaultCombinedViewFormatter(); GrantleeViewFormatter *mGrantleeViewFormatter = nullptr; }; } #endif // DEFAULTCOMBINEDVIEWFORMATTER_H diff --git a/src/formatter/defaultnormalviewformatter.cpp b/src/formatter/defaultnormalviewformatter.cpp index 0849e5e6..b3c814a6 100644 --- a/src/formatter/defaultnormalviewformatter.cpp +++ b/src/formatter/defaultnormalviewformatter.cpp @@ -1,101 +1,100 @@ /* Copyright (C) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "defaultnormalviewformatter.h" #include "akregatorconfig.h" #include "grantleeviewformatter.h" #include "article.h" #include "feed.h" #include "folder.h" #include "treenode.h" #include "treenodevisitor.h" #include "utils.h" #include #include using namespace Syndication; using namespace Akregator; class DefaultNormalViewFormatter::SummaryVisitor : public TreeNodeVisitor { public: SummaryVisitor(DefaultNormalViewFormatter *p) : parent(p) { } ~SummaryVisitor() override { } bool visitFeed(Feed *node) override { text = parent->mGrantleeViewFormatter->formatFeed(node); return true; } bool visitFolder(Folder *node) override { text = parent->mGrantleeViewFormatter->formatFolder(node); return true; } QString formatSummary(TreeNode *node) { text.clear(); visit(node); return text; } QString text; private: DefaultNormalViewFormatter *parent = nullptr; }; -DefaultNormalViewFormatter::DefaultNormalViewFormatter(const QString &grantleeDirectory, const QUrl &imageDir, QPaintDevice *device) +DefaultNormalViewFormatter::DefaultNormalViewFormatter(const QString &grantleeDirectory, QPaintDevice *device) : ArticleFormatter() - , m_imageDir(imageDir) , m_summaryVisitor(new SummaryVisitor(this)) { m_DefaultThemePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("akregator/grantleetheme/%1/").arg(grantleeDirectory), QStandardPaths::LocateDirectory); - mGrantleeViewFormatter = new GrantleeViewFormatter(QStringLiteral("normalview.html"), m_DefaultThemePath, m_imageDir, device->logicalDpiY()); + mGrantleeViewFormatter = new GrantleeViewFormatter(QStringLiteral("normalview.html"), m_DefaultThemePath, device->logicalDpiY()); } DefaultNormalViewFormatter::~DefaultNormalViewFormatter() { delete mGrantleeViewFormatter; delete m_summaryVisitor; } QString DefaultNormalViewFormatter::formatSummary(TreeNode *node) const { return m_summaryVisitor->formatSummary(node); } QString DefaultNormalViewFormatter::formatArticles(const QVector
&articles, IconOption icon) const { if (articles.count() != 1) { return {}; } return mGrantleeViewFormatter->formatArticles(articles, icon); } diff --git a/src/formatter/defaultnormalviewformatter.h b/src/formatter/defaultnormalviewformatter.h index 1f339c57..af08209b 100644 --- a/src/formatter/defaultnormalviewformatter.h +++ b/src/formatter/defaultnormalviewformatter.h @@ -1,50 +1,48 @@ /* Copyright (C) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DEFAULTNORMALVIEWFORMATTER_H #define DEFAULTNORMALVIEWFORMATTER_H #include "articleformatter.h" #include "akregator_export.h" #include class QPaintDevice; namespace Akregator { class GrantleeViewFormatter; class AKREGATOR_EXPORT DefaultNormalViewFormatter : public ArticleFormatter { public: - explicit DefaultNormalViewFormatter(const QString &grantleeDirectory, const QUrl &imageDir, QPaintDevice *device = nullptr); + explicit DefaultNormalViewFormatter(const QString &grantleeDirectory, QPaintDevice *device = nullptr); ~DefaultNormalViewFormatter() override; QString formatArticles(const QVector
&article, IconOption option) const override; QString formatSummary(TreeNode *node) const override; private: DefaultNormalViewFormatter(); - - QUrl m_imageDir; QString m_DefaultThemePath; class SummaryVisitor; SummaryVisitor *m_summaryVisitor = nullptr; GrantleeViewFormatter *mGrantleeViewFormatter = nullptr; }; } #endif // DEFAULTNORMALVIEWFORMATTER_H diff --git a/src/formatter/grantleeutil.cpp b/src/formatter/grantleeutil.cpp index 2b9b9552..f4d7348d 100644 --- a/src/formatter/grantleeutil.cpp +++ b/src/formatter/grantleeutil.cpp @@ -1,31 +1,26 @@ /* Copyright (C) 2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "grantleeutil.h" #include "feed.h" -#include "utils.h" - -QString Akregator::GrantleeUtil::imageFeed(const Feed *feed, const QUrl &imageDir) +#include +QString Akregator::GrantleeUtil::imageFeed(const Feed *feed) { - QString file = Akregator::Utils::fileNameForUrl(feed->xmlUrl()); - QUrl u(imageDir); - u = u.adjusted(QUrl::RemoveFilename); - u.setPath(u.path() + file); - return QStringLiteral("\n").arg(feed->htmlUrl(), u.url()); + return QStringLiteral("\n").arg(feed->htmlUrl(), feed->logoUrl()); } diff --git a/src/formatter/grantleeutil.h b/src/formatter/grantleeutil.h index 8b2b71dc..2a724fe1 100644 --- a/src/formatter/grantleeutil.h +++ b/src/formatter/grantleeutil.h @@ -1,32 +1,32 @@ /* Copyright (C) 2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef GRANTLEEUTIL_H #define GRANTLEEUTIL_H #include #include namespace Akregator { class Feed; namespace GrantleeUtil { -QString imageFeed(const Feed *feed, const QUrl &imageDir); +QString imageFeed(const Feed *feed); } } #endif // GRANTLEEUTIL_H diff --git a/src/formatter/grantleeviewformatter.cpp b/src/formatter/grantleeviewformatter.cpp index d6b0673f..36502982 100644 --- a/src/formatter/grantleeviewformatter.cpp +++ b/src/formatter/grantleeviewformatter.cpp @@ -1,163 +1,162 @@ /* Copyright (C) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "grantleeviewformatter.h" #include "articlegrantleeobject.h" #include "utils.h" #include "akregatorconfig.h" #include "grantleeutil.h" #include #include #include #include #include #include #include using namespace Akregator; -GrantleeViewFormatter::GrantleeViewFormatter(const QString &htmlFileName, const QString &themePath, const QUrl &imageDir, int deviceDpiY) +GrantleeViewFormatter::GrantleeViewFormatter(const QString &htmlFileName, const QString &themePath, int deviceDpiY) : GrantleeTheme::GenericFormatter(htmlFileName, themePath) - , mImageDir(imageDir) , mHtmlArticleFileName(htmlFileName) , mGrantleeThemePath(QStringLiteral("file://") + themePath + QLatin1Char('/')) , mDeviceDpiY(deviceDpiY) { mDirectionString = QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr"); setApplicationDomain("akregator"); } GrantleeViewFormatter::~GrantleeViewFormatter() { } int GrantleeViewFormatter::pointsToPixel(int pointSize) const { return (pointSize * mDeviceDpiY + 36) / 72; } void GrantleeViewFormatter::addStandardObject(QVariantHash &grantleeObject) { grantleeObject.insert(QStringLiteral("absoluteThemePath"), mGrantleeThemePath); grantleeObject.insert(QStringLiteral("applicationDir"), mDirectionString); grantleeObject.insert(QStringLiteral("standardFamilyFont"), Settings::standardFont()); grantleeObject.insert(QStringLiteral("mediumFontSize"), pointsToPixel(Settings::mediumFontSize())); } QString GrantleeViewFormatter::formatFeed(Akregator::Feed *feed) { setDefaultHtmlMainFile(QStringLiteral("defaultnormalvisitfeed.html")); if (!errorMessage().isEmpty()) { return errorMessage(); } QVariantHash feedObject; addStandardObject(feedObject); feedObject.insert(QStringLiteral("strippedTitle"), Utils::stripTags(feed->title())); QString numberOfArticle; if (feed->unread() == 0) { numberOfArticle = i18n(" (no unread articles)"); } else { numberOfArticle = i18np(" (1 unread article)", " (%1 unread articles)", feed->unread()); } feedObject.insert(QStringLiteral("feedCount"), numberOfArticle); QString feedImage; - if (!feed->image().isNull()) { // image - feedImage = GrantleeUtil::imageFeed(feed, mImageDir); + if (!feed->logoUrl().isEmpty()) { // image + feedImage = GrantleeUtil::imageFeed(feed); } else { feedImage = QStringLiteral("
"); } feedObject.insert(QStringLiteral("feedImage"), feedImage); if (!feed->description().isEmpty()) { QString feedDescription; feedDescription = QStringLiteral("
").arg(mDirectionString); feedDescription += i18n("Description: %1
", feed->description()); feedDescription += QStringLiteral("
"); // /description feedObject.insert(QStringLiteral("feedDescription"), feedDescription); } if (!feed->htmlUrl().isEmpty()) { QString feedHomePage; feedHomePage = QStringLiteral("
").arg(mDirectionString); feedHomePage += i18n("Homepage: %1", feed->htmlUrl()); feedHomePage += QStringLiteral("
"); // / link feedObject.insert(QStringLiteral("feedHomePage"), feedHomePage); } return render(feedObject); } QString GrantleeViewFormatter::formatFolder(Akregator::Folder *node) { setDefaultHtmlMainFile(QStringLiteral("defaultnormalvisitfolder.html")); if (!errorMessage().isEmpty()) { return errorMessage(); } QVariantHash folderObject; addStandardObject(folderObject); folderObject.insert(QStringLiteral("nodeTitle"), node->title()); QString numberOfArticle; if (node->unread() == 0) { numberOfArticle = i18n(" (no unread articles)"); } else { numberOfArticle = i18np(" (1 unread article)", " (%1 unread articles)", node->unread()); } folderObject.insert(QStringLiteral("nodeCount"), numberOfArticle); return render(folderObject); } QString GrantleeViewFormatter::formatArticles(const QVector
&article, ArticleFormatter::IconOption icon) { setDefaultHtmlMainFile(mHtmlArticleFileName); if (!errorMessage().isEmpty()) { return errorMessage(); } QVariantHash articleObject; QVariantList articlesList; const int nbArticles(article.count()); articlesList.reserve(nbArticles); QList lstObj; lstObj.reserve(nbArticles); for (int i = 0; i < nbArticles; ++i) { - ArticleGrantleeObject *articleObj = new ArticleGrantleeObject(mImageDir, article.at(i), icon); + ArticleGrantleeObject *articleObj = new ArticleGrantleeObject(article.at(i), icon); articlesList << QVariant::fromValue(static_cast(articleObj)); lstObj.append(articleObj); } articleObject.insert(QStringLiteral("articles"), articlesList); addStandardObject(articleObject); articleObject.insert(QStringLiteral("dateI18n"), i18n("Date")); articleObject.insert(QStringLiteral("commentI18n"), i18n("Comment")); articleObject.insert(QStringLiteral("completeStoryI18n"), i18n("Complete Story")); articleObject.insert(QStringLiteral("authorI18n"), i18n("Author")); articleObject.insert(QStringLiteral("enclosureI18n"), i18n("Enclosure")); const QString str = render(articleObject); qDeleteAll(lstObj); return str; } diff --git a/src/formatter/grantleeviewformatter.h b/src/formatter/grantleeviewformatter.h index 3e55ca92..87a1ab2f 100644 --- a/src/formatter/grantleeviewformatter.h +++ b/src/formatter/grantleeviewformatter.h @@ -1,49 +1,48 @@ /* Copyright (C) 2016-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef GRANTLEEVIEWFORMATTER_H #define GRANTLEEVIEWFORMATTER_H #include #include "article.h" #include "articleformatter.h" #include namespace Akregator { class Folder; class GrantleeViewFormatter : public GrantleeTheme::GenericFormatter { public: - explicit GrantleeViewFormatter(const QString &htmlFileName, const QString &themePath, const QUrl &imageDir, int deviceDpiY); + explicit GrantleeViewFormatter(const QString &htmlFileName, const QString &themePath, int deviceDpiY); ~GrantleeViewFormatter(); QString formatArticles(const QVector
&article, ArticleFormatter::IconOption icon); QString formatFolder(Akregator::Folder *node); QString formatFeed(Akregator::Feed *feed); private: void addStandardObject(QVariantHash &grantleeObject); int pointsToPixel(int pointSize) const; - QUrl mImageDir; QString mHtmlArticleFileName; QString mDirectionString; QString mGrantleeThemePath; int mDeviceDpiY; }; } #endif // GRANTLEEVIEWFORMATTER_H diff --git a/src/utils.cpp b/src/utils.cpp index 2f549738..9633fcbc 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,61 +1,49 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "utils.h" #include #include #include #include using namespace Akregator; QString Utils::convertHtmlTags(const QString &title) { QTextDocument newText; newText.setHtml(title); return newText.toPlainText(); } QString Utils::stripTags(QString str) { return str.remove(QRegExp(QLatin1String("<[^>]*>"))); } uint Utils::calcHash(const QString &str) { const QByteArray array = str.toLatin1(); return qChecksum(array.constData(), array.size()); } -QString Utils::fileNameForUrl(const QString &url_p) -{ - QString url2(url_p); - - url2 = url2.replace(QLatin1Char('/'), QLatin1Char('_')).replace(QLatin1Char(':'), QLatin1Char('_')); - - if (url2.length() > 255) { - url2 = url2.left(200) + QString::number(Akregator::Utils::calcHash(url2), 16); - } - - return url2; -} diff --git a/src/utils.h b/src/utils.h index dab6d04c..75bcaf8a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,59 +1,51 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_UTILS_H #define AKREGATOR_UTILS_H #include "akregator_export.h" #include typedef unsigned int uint; namespace Akregator { class AKREGATOR_EXPORT Utils { public: /** removes HTML/XML tags (everything between < and >) from a string. "

foo bar

" becomes "foo bar" */ static QString stripTags(QString str); /** taken from some website... -fo * djb2 * This algorithm was first reported by Dan Bernstein * many years ago in comp.lang.c */ static uint calcHash(const QString &str); - /** - * returns a file name for a URL, with chars like "/" ":" - * replaced by "_". Too long URLs (>255 chars) are shortened and - * appended with a hash value. - * - */ - static QString fileNameForUrl(const QString &url); - static QString convertHtmlTags(const QString &title); }; } // namespace Akregator #endif // AKREGATOR_UTILS_H