diff --git a/src/base/rss/feed_serializer.cpp b/src/base/rss/feed_serializer.cpp index bd875e3ad..d22fae28d 100644 --- a/src/base/rss/feed_serializer.cpp +++ b/src/base/rss/feed_serializer.cpp @@ -42,6 +42,8 @@ #include "base/utils/io.h" #include "rss_article.h" +const int ARTICLEDATALIST_TYPEID = qRegisterMetaType>(); + void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString &url) { QFile file {dataFileName.data()}; @@ -122,5 +124,10 @@ QVector RSS::Private::FeedSerializer::loadArticles(const QByteArra result.push_back(varHash); } + std::sort(result.begin(), result.end(), [](const QVariantHash &left, const QVariantHash &right) + { + return (left.value(Article::KeyDate).toDateTime() > right.value(Article::KeyDate).toDateTime()); + }); + return result; } diff --git a/src/base/rss/rss_article.cpp b/src/base/rss/rss_article.cpp index 42894b57c..7d6eb9278 100644 --- a/src/base/rss/rss_article.cpp +++ b/src/base/rss/rss_article.cpp @@ -30,7 +30,6 @@ #include "rss_article.h" -#include #include #include "base/global.h" @@ -38,19 +37,6 @@ using namespace RSS; -namespace -{ - QVariantHash articleDataFromJSON(const QJsonObject &jsonObj) - { - auto varHash = jsonObj.toVariantHash(); - // JSON object store DateTime as string so we need to convert it - varHash[Article::KeyDate] = - QDateTime::fromString(jsonObj.value(Article::KeyDate).toString(), Qt::RFC2822Date); - - return varHash; - } -} - const QString Article::KeyId = u"id"_qs; const QString Article::KeyDate = u"date"_qs; const QString Article::KeyTitle = u"title"_qs; @@ -75,11 +61,6 @@ Article::Article(Feed *feed, const QVariantHash &varHash) { } -Article::Article(Feed *feed, const QJsonObject &jsonObj) - : Article(feed, articleDataFromJSON(jsonObj)) -{ -} - QString Article::guid() const { return m_guid; @@ -135,15 +116,6 @@ void Article::markAsRead() } } -QJsonObject Article::toJsonObject() const -{ - auto jsonObj = QJsonObject::fromVariantHash(m_data); - // JSON object doesn't support DateTime so we need to convert it - jsonObj[KeyDate] = m_date.toString(Qt::RFC2822Date); - - return jsonObj; -} - bool Article::articleDateRecentThan(const Article *article, const QDateTime &date) { return article->date() > date; diff --git a/src/base/rss/rss_article.h b/src/base/rss/rss_article.h index 702e0e171..717613529 100644 --- a/src/base/rss/rss_article.h +++ b/src/base/rss/rss_article.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2017 Vladimir Golovnev + * Copyright (C) 2017-2022 Vladimir Golovnev * Copyright (C) 2010 Christophe Dumez * Copyright (C) 2010 Arnaud Demaiziere * @@ -39,7 +39,7 @@ namespace RSS { class Feed; - class Article : public QObject + class Article final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(Article) @@ -47,7 +47,6 @@ namespace RSS friend class Feed; Article(Feed *feed, const QVariantHash &varHash); - Article(Feed *feed, const QJsonObject &jsonObj); public: static const QString KeyId; @@ -72,8 +71,6 @@ namespace RSS void markAsRead(); - QJsonObject toJsonObject() const; - static bool articleDateRecentThan(const Article *article, const QDateTime &date); signals: diff --git a/src/base/rss/rss_autodownloader.cpp b/src/base/rss/rss_autodownloader.cpp index 36b1ec5f0..26498174f 100644 --- a/src/base/rss/rss_autodownloader.cpp +++ b/src/base/rss/rss_autodownloader.cpp @@ -491,7 +491,12 @@ void AutoDownloader::resetProcessingQueue() void AutoDownloader::startProcessing() { resetProcessingQueue(); - connect(Session::instance()->rootFolder(), &Folder::newArticle, this, &AutoDownloader::handleNewArticle); + + const RSS::Folder *rootFolder = Session::instance()->rootFolder(); + for (const Article *article : asConst(rootFolder->articles())) + handleNewArticle(article); + + connect(rootFolder, &Folder::newArticle, this, &AutoDownloader::handleNewArticle); } void AutoDownloader::setProcessingEnabled(const bool enabled) diff --git a/src/base/rss/rss_feed.cpp b/src/base/rss/rss_feed.cpp index 14d419343..de75bb367 100644 --- a/src/base/rss/rss_feed.cpp +++ b/src/base/rss/rss_feed.cpp @@ -222,7 +222,10 @@ void Feed::handleDownloadFinished(const Net::DownloadResult &result) LogMsg(tr("RSS feed at '%1' is successfully downloaded. Starting to parse it.") .arg(result.url)); // Parse the download RSS - m_parser->parse(result.data); + QMetaObject::invokeMethod(m_parser, [this, data = result.data]() + { + m_parser->parse(data); + }); } else { @@ -306,18 +309,18 @@ void Feed::storeDeferred() m_savingTimer.start(5 * 1000, this); } -bool Feed::addArticle(Article *article) +bool Feed::addArticle(const QVariantHash &articleData) { - Q_ASSERT(article); - Q_ASSERT(!m_articles.contains(article->guid())); + Q_ASSERT(!m_articles.contains(articleData.value(Article::KeyId).toString())); // Insertion sort const int maxArticles = m_session->maxArticlesPerFeed(); const auto lowerBound = std::lower_bound(m_articlesByDate.begin(), m_articlesByDate.end() - , article->date(), Article::articleDateRecentThan); + , articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan); if ((lowerBound - m_articlesByDate.begin()) >= maxArticles) return false; // we reach max articles + auto *article = new Article(this, articleData); m_articles[article->guid()] = article; m_articlesByDate.insert(lowerBound, article); if (!article->isRead()) @@ -434,7 +437,7 @@ int Feed::updateArticles(const QList &loadedArticles) { if (a.second) { - addArticle(new Article {this, *a.second}); + addArticle(*a.second); ++newArticlesCount; } }); @@ -462,7 +465,12 @@ QJsonValue Feed::toJsonValue(const bool withData) const QJsonArray jsonArr; for (Article *article : asConst(m_articles)) - jsonArr << article->toJsonObject(); + { + auto articleObj = QJsonObject::fromVariantHash(article->data()); + // JSON object doesn't support DateTime so we need to convert it + articleObj[Article::KeyDate] = article->date().toString(Qt::RFC2822Date); + jsonArr.append(articleObj); + } jsonObj.insert(KEY_ARTICLES, jsonArr); } @@ -489,19 +497,40 @@ void Feed::handleArticleRead(Article *article) storeDeferred(); } -void Feed::handleArticleLoadFinished(const QVector &articles) +void Feed::handleArticleLoadFinished(QVector articles) { - for (const QVariantHash &data : articles) + Q_ASSERT(m_articles.isEmpty()); + Q_ASSERT(m_unreadCount == 0); + + const int maxArticles = m_session->maxArticlesPerFeed(); + if (articles.size() > maxArticles) + articles.resize(maxArticles); + + m_articles.reserve(articles.size()); + m_articlesByDate.reserve(articles.size()); + + for (const QVariantHash &articleData : articles) { - try + const auto articleID = articleData.value(Article::KeyId).toString(); + // TODO: use [[unlikely]] in C++20 + if (Q_UNLIKELY(m_articles.contains(articleID))) + continue; + + auto *article = new Article(this, articleData); + m_articles[articleID] = article; + m_articlesByDate.append(article); + if (!article->isRead()) { - auto *article = new Article(this, data); - if (!addArticle(article)) - delete article; + ++m_unreadCount; + connect(article, &Article::read, this, &Feed::handleArticleRead); } - catch (const RuntimeError &) {} + + emit newArticle(article); } + if (m_unreadCount > 0) + emit unreadCountChanged(this); + m_isInitialized = true; emit stateChanged(this); diff --git a/src/base/rss/rss_feed.h b/src/base/rss/rss_feed.h index dfeab9e2f..715dbf1f1 100644 --- a/src/base/rss/rss_feed.h +++ b/src/base/rss/rss_feed.h @@ -99,7 +99,7 @@ namespace RSS void handleDownloadFinished(const Net::DownloadResult &result); void handleParsingFinished(const Private::ParsingResult &result); void handleArticleRead(Article *article); - void handleArticleLoadFinished(const QVector &articles); + void handleArticleLoadFinished(QVector articles); private: void timerEvent(QTimerEvent *event) override; @@ -107,7 +107,7 @@ namespace RSS void load(); void store(); void storeDeferred(); - bool addArticle(Article *article); + bool addArticle(const QVariantHash &articleData); void removeOldestArticle(); void increaseUnreadCount(); void decreaseUnreadCount(); diff --git a/src/base/rss/rss_parser.cpp b/src/base/rss/rss_parser.cpp index 58df20309..ea7ae63dd 100644 --- a/src/base/rss/rss_parser.cpp +++ b/src/base/rss/rss_parser.cpp @@ -539,23 +539,15 @@ namespace } } -using namespace RSS::Private; +const int PARSINGRESULT_TYPEID = qRegisterMetaType(); -const int ParsingResultTypeId = qRegisterMetaType(); - -Parser::Parser(const QString lastBuildDate) +RSS::Private::Parser::Parser(const QString lastBuildDate) { m_result.lastBuildDate = lastBuildDate; } -void Parser::parse(const QByteArray &feedData) -{ - QMetaObject::invokeMethod(this, [this, feedData]() { parse_impl(feedData); } - , Qt::QueuedConnection); -} - // read and create items from a rss document -void Parser::parse_impl(const QByteArray &feedData) +void RSS::Private::Parser::parse(const QByteArray &feedData) { QXmlStreamReader xml(feedData); XmlStreamEntityResolver resolver; @@ -608,7 +600,7 @@ void Parser::parse_impl(const QByteArray &feedData) m_articleIDs.clear(); } -void Parser::parseRssArticle(QXmlStreamReader &xml) +void RSS::Private::Parser::parseRssArticle(QXmlStreamReader &xml) { QVariantHash article; QString altTorrentUrl; @@ -671,7 +663,7 @@ void Parser::parseRssArticle(QXmlStreamReader &xml) addArticle(article); } -void Parser::parseRSSChannel(QXmlStreamReader &xml) +void RSS::Private::Parser::parseRSSChannel(QXmlStreamReader &xml) { while (!xml.atEnd()) { @@ -704,7 +696,7 @@ void Parser::parseRSSChannel(QXmlStreamReader &xml) } } -void Parser::parseAtomArticle(QXmlStreamReader &xml) +void RSS::Private::Parser::parseAtomArticle(QXmlStreamReader &xml) { QVariantHash article; bool doubleContent = false; @@ -785,7 +777,7 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml) addArticle(article); } -void Parser::parseAtomChannel(QXmlStreamReader &xml) +void RSS::Private::Parser::parseAtomChannel(QXmlStreamReader &xml) { m_baseUrl = xml.attributes().value(u"xml:base"_qs).toString(); @@ -820,7 +812,7 @@ void Parser::parseAtomChannel(QXmlStreamReader &xml) } } -void Parser::addArticle(QVariantHash article) +void RSS::Private::Parser::addArticle(QVariantHash article) { QVariant &torrentURL = article[Article::KeyTorrentURL]; if (torrentURL.toString().isEmpty()) diff --git a/src/base/rss/rss_parser.h b/src/base/rss/rss_parser.h index 675e5c9b3..d5fc4f938 100644 --- a/src/base/rss/rss_parser.h +++ b/src/base/rss/rss_parser.h @@ -37,43 +37,39 @@ class QXmlStreamReader; -namespace RSS +namespace RSS::Private { - namespace Private + struct ParsingResult { - struct ParsingResult - { - QString error; - QString lastBuildDate; - QString title; - QList articles; - }; + QString error; + QString lastBuildDate; + QString title; + QList articles; + }; - class Parser : public QObject - { - Q_OBJECT - Q_DISABLE_COPY_MOVE(Parser) + class Parser final : public QObject + { + Q_OBJECT + Q_DISABLE_COPY_MOVE(Parser) - public: - explicit Parser(QString lastBuildDate); - void parse(const QByteArray &feedData); + public: + explicit Parser(QString lastBuildDate); + void parse(const QByteArray &feedData); - signals: - void finished(const RSS::Private::ParsingResult &result); + signals: + void finished(const RSS::Private::ParsingResult &result); - private: - Q_INVOKABLE void parse_impl(const QByteArray &feedData); - void parseRssArticle(QXmlStreamReader &xml); - void parseRSSChannel(QXmlStreamReader &xml); - void parseAtomArticle(QXmlStreamReader &xml); - void parseAtomChannel(QXmlStreamReader &xml); - void addArticle(QVariantHash article); + private: + void parseRssArticle(QXmlStreamReader &xml); + void parseRSSChannel(QXmlStreamReader &xml); + void parseAtomArticle(QXmlStreamReader &xml); + void parseAtomChannel(QXmlStreamReader &xml); + void addArticle(QVariantHash article); - QString m_baseUrl; - ParsingResult m_result; - QSet m_articleIDs; - }; - } + QString m_baseUrl; + ParsingResult m_result; + QSet m_articleIDs; + }; } Q_DECLARE_METATYPE(RSS::Private::ParsingResult)