Browse Source

Do RSS serializing on worker thread

PR #16357.
adaptive-webui-19844
Prince Gupta 2 years ago committed by GitHub
parent
commit
c47e29c7c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/base/CMakeLists.txt
  2. 2
      src/base/base.pri
  3. 126
      src/base/rss/feed_serializer.cpp
  4. 60
      src/base/rss/feed_serializer.h
  5. 140
      src/base/rss/rss_feed.cpp
  6. 15
      src/base/rss/rss_feed.h
  7. 8
      src/base/rss/rss_session.cpp

2
src/base/CMakeLists.txt

@ -70,6 +70,7 @@ add_library(qbt_base STATIC
preferences.h preferences.h
profile.h profile.h
profile_p.h profile_p.h
rss/feed_serializer.h
rss/rss_article.h rss/rss_article.h
rss/rss_autodownloader.h rss/rss_autodownloader.h
rss/rss_autodownloadrule.h rss/rss_autodownloadrule.h
@ -152,6 +153,7 @@ add_library(qbt_base STATIC
preferences.cpp preferences.cpp
profile.cpp profile.cpp
profile_p.cpp profile_p.cpp
rss/feed_serializer.cpp
rss/rss_article.cpp rss/rss_article.cpp
rss/rss_autodownloader.cpp rss/rss_autodownloader.cpp
rss/rss_autodownloadrule.cpp rss/rss_autodownloadrule.cpp

2
src/base/base.pri

@ -69,6 +69,7 @@ HEADERS += \
$$PWD/preferences.h \ $$PWD/preferences.h \
$$PWD/profile.h \ $$PWD/profile.h \
$$PWD/profile_p.h \ $$PWD/profile_p.h \
$$PWD/rss/feed_serializer.h \
$$PWD/rss/rss_article.h \ $$PWD/rss/rss_article.h \
$$PWD/rss/rss_autodownloader.h \ $$PWD/rss/rss_autodownloader.h \
$$PWD/rss/rss_autodownloadrule.h \ $$PWD/rss/rss_autodownloadrule.h \
@ -152,6 +153,7 @@ SOURCES += \
$$PWD/preferences.cpp \ $$PWD/preferences.cpp \
$$PWD/profile.cpp \ $$PWD/profile.cpp \
$$PWD/profile_p.cpp \ $$PWD/profile_p.cpp \
$$PWD/rss/feed_serializer.cpp \
$$PWD/rss/rss_article.cpp \ $$PWD/rss/rss_article.cpp \
$$PWD/rss/rss_autodownloader.cpp \ $$PWD/rss/rss_autodownloader.cpp \
$$PWD/rss/rss_autodownloadrule.cpp \ $$PWD/rss/rss_autodownloadrule.cpp \

126
src/base/rss/feed_serializer.cpp

@ -0,0 +1,126 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Prince Gupta <guptaprince8832@gmail.com>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "feed_serializer.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QVector>
#include "base/logger.h"
#include "base/path.h"
#include "base/utils/io.h"
#include "rss_article.h"
void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString &url)
{
QFile file {dataFileName.data()};
if (!file.exists())
{
emit loadingFinished({});
}
else if (file.open(QFile::ReadOnly))
{
emit loadingFinished(loadArticles(file.readAll(), url));
file.close();
}
else
{
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
.arg(dataFileName.toString(), file.errorString())
, Log::WARNING);
}
}
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector<QVariantHash> &articlesData)
{
QJsonArray arr;
for (const QVariantHash &data : articlesData)
{
auto jsonObj = QJsonObject::fromVariantHash(data);
// JSON object doesn't support DateTime so we need to convert it
jsonObj[Article::KeyDate] = data[Article::KeyDate].toDateTime().toString(Qt::RFC2822Date);
arr << jsonObj;
}
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(dataFileName, QJsonDocument(arr).toJson());
if (!result)
{
LogMsg(tr("Failed to save RSS feed in '%1', Reason: %2").arg(dataFileName.toString(), result.error())
, Log::WARNING);
}
}
QVector<QVariantHash> RSS::Private::FeedSerializer::loadArticles(const QByteArray &data, const QString &url)
{
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Couldn't parse RSS Session data. Error: %1").arg(jsonError.errorString())
, Log::WARNING);
return {};
}
if (!jsonDoc.isArray())
{
LogMsg(tr("Couldn't load RSS Session data. Invalid data format."), Log::WARNING);
return {};
}
QVector<QVariantHash> result;
const QJsonArray jsonArr = jsonDoc.array();
result.reserve(jsonArr.size());
for (int i = 0; i < jsonArr.size(); ++i)
{
const QJsonValue jsonVal = jsonArr[i];
if (!jsonVal.isObject())
{
LogMsg(tr("Couldn't load RSS article '%1#%2'. Invalid data format.")
.arg(url, QString::number(i)), Log::WARNING);
continue;
}
const auto jsonObj = jsonVal.toObject();
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);
result.push_back(varHash);
}
return result;
}

60
src/base/rss/feed_serializer.h

@ -0,0 +1,60 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Prince Gupta <guptaprince8832@gmail.com>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QtContainerFwd>
#include <QObject>
#include <QString>
#include <QVariantHash>
#include "base/pathfwd.h"
namespace RSS::Private
{
class FeedSerializer final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(FeedSerializer)
public:
using QObject::QObject;
void load(const Path &dataFileName, const QString &url);
void store(const Path &dataFileName, const QVector<QVariantHash> &articlesData);
signals:
void loadingFinished(const QVector<QVariantHash> &articles);
private:
QVector<QVariantHash> loadArticles(const QByteArray &data, const QString &url);
};
}

140
src/base/rss/rss_feed.cpp

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2017 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org> * Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
* *
@ -34,12 +34,12 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <QDir>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue> #include <QJsonValue>
#include <QUrl> #include <QUrl>
#include <QVector>
#include "base/asyncfilestorage.h" #include "base/asyncfilestorage.h"
#include "base/global.h" #include "base/global.h"
@ -47,6 +47,7 @@
#include "base/net/downloadmanager.h" #include "base/net/downloadmanager.h"
#include "base/profile.h" #include "base/profile.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "feed_serializer.h"
#include "rss_article.h" #include "rss_article.h"
#include "rss_parser.h" #include "rss_parser.h"
#include "rss_session.h" #include "rss_session.h"
@ -79,6 +80,11 @@ Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *s
m_iconPath = storageDir / Path(uidHex + u".ico"); m_iconPath = storageDir / Path(uidHex + u".ico");
m_serializer = new Private::FeedSerializer;
m_serializer->moveToThread(m_session->workingThread());
connect(this, &Feed::destroyed, m_serializer, &Private::FeedSerializer::deleteLater);
connect(m_serializer, &Private::FeedSerializer::loadingFinished, this, &Feed::handleArticleLoadFinished);
m_parser = new Private::Parser(m_lastBuildDate); m_parser = new Private::Parser(m_lastBuildDate);
m_parser->moveToThread(m_session->workingThread()); m_parser->moveToThread(m_session->workingThread());
connect(this, &Feed::destroyed, m_parser, &Private::Parser::deleteLater); connect(this, &Feed::destroyed, m_parser, &Private::Parser::deleteLater);
@ -98,6 +104,7 @@ Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *s
Feed::~Feed() Feed::~Feed()
{ {
store();
emit aboutToBeDestroyed(this); emit aboutToBeDestroyed(this);
} }
@ -130,6 +137,12 @@ void Feed::markAsRead()
void Feed::refresh() void Feed::refresh()
{ {
if (!m_isInitialized)
{
m_pendingRefresh = true;
return;
}
if (m_downloadHandler) if (m_downloadHandler)
m_downloadHandler->cancel(); m_downloadHandler->cancel();
@ -162,7 +175,7 @@ QString Feed::title() const
bool Feed::isLoading() const bool Feed::isLoading() const
{ {
return m_isLoading; return m_isLoading || !m_isInitialized;
} }
QString Feed::lastBuildDate() const QString Feed::lastBuildDate() const
@ -261,100 +274,30 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
void Feed::load() void Feed::load()
{ {
QFile file {(m_session->dataFileStorage()->storageDir() / m_dataFileName).data()}; QMetaObject::invokeMethod(m_serializer, [this]()
if (!file.exists())
{
loadArticlesLegacy();
m_dirty = true;
store(); // convert to new format
}
else if (file.open(QFile::ReadOnly))
{
loadArticles(file.readAll());
file.close();
}
else
{
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
.arg(m_dataFileName.toString(), file.errorString())
, Log::WARNING);
}
}
void Feed::loadArticles(const QByteArray &data)
{
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{ {
LogMsg(tr("Couldn't parse RSS Session data. Error: %1").arg(jsonError.errorString()) m_serializer->load((m_session->dataFileStorage()->storageDir() / m_dataFileName), m_url);
, Log::WARNING); });
return;
}
if (!jsonDoc.isArray())
{
LogMsg(tr("Couldn't load RSS Session data. Invalid data format."), Log::WARNING);
return;
}
const QJsonArray jsonArr = jsonDoc.array();
int i = -1;
for (const QJsonValue &jsonVal : jsonArr)
{
++i;
if (!jsonVal.isObject())
{
LogMsg(tr("Couldn't load RSS article '%1#%2'. Invalid data format.").arg(m_url).arg(i)
, Log::WARNING);
continue;
}
try
{
auto article = new Article(this, jsonVal.toObject());
if (!addArticle(article))
delete article;
}
catch (const RuntimeError &) {}
}
}
void Feed::loadArticlesLegacy()
{
const SettingsPtr qBTRSSFeeds = Profile::instance()->applicationSettings(u"qBittorrent-rss-feeds"_qs);
const QVariantHash allOldItems = qBTRSSFeeds->value(u"old_items"_qs).toHash();
for (const QVariant &var : asConst(allOldItems.value(m_url).toList()))
{
auto hash = var.toHash();
// update legacy keys
hash[Article::KeyLink] = hash.take(u"news_link"_qs);
hash[Article::KeyTorrentURL] = hash.take(u"torrent_url"_qs);
hash[Article::KeyIsRead] = hash.take(u"read"_qs);
try
{
auto article = new Article(this, hash);
if (!addArticle(article))
delete article;
}
catch (const RuntimeError &) {}
}
} }
void Feed::store() void Feed::store()
{ {
if (!m_dirty) return; if (!m_dirty)
return;
m_dirty = false; m_dirty = false;
m_savingTimer.stop(); m_savingTimer.stop();
QJsonArray jsonArr; QVector<QVariantHash> articlesData;
articlesData.reserve(m_articles.size());
for (Article *article :asConst(m_articles)) for (Article *article :asConst(m_articles))
jsonArr << article->toJsonObject(); articlesData.push_back(article->data());
m_session->dataFileStorage()->store(m_dataFileName, QJsonDocument(jsonArr).toJson()); QMetaObject::invokeMethod(m_serializer, [this, articlesData]()
{
m_serializer->store((m_session->dataFileStorage()->storageDir() / m_dataFileName), articlesData);
});
} }
void Feed::storeDeferred() void Feed::storeDeferred()
@ -546,8 +489,33 @@ void Feed::handleArticleRead(Article *article)
storeDeferred(); storeDeferred();
} }
void Feed::handleArticleLoadFinished(const QVector<QVariantHash> &articles)
{
for (const QVariantHash &data : articles)
{
try
{
auto *article = new Article(this, data);
if (!addArticle(article))
delete article;
}
catch (const RuntimeError &) {}
}
m_isInitialized = true;
emit stateChanged(this);
if (m_pendingRefresh)
{
m_pendingRefresh = false;
refresh();
}
}
void Feed::cleanup() void Feed::cleanup()
{ {
m_dirty = false;
m_savingTimer.stop();
Utils::Fs::removeFile(m_session->dataFileStorage()->storageDir() / m_dataFileName); Utils::Fs::removeFile(m_session->dataFileStorage()->storageDir() / m_dataFileName);
Utils::Fs::removeFile(m_iconPath); Utils::Fs::removeFile(m_iconPath);
} }

15
src/base/rss/rss_feed.h

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2017 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org> * Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
* *
@ -30,10 +30,12 @@
#pragma once #pragma once
#include <QtContainerFwd>
#include <QBasicTimer> #include <QBasicTimer>
#include <QHash> #include <QHash>
#include <QList> #include <QList>
#include <QUuid> #include <QUuid>
#include <QVariantHash>
#include "base/path.h" #include "base/path.h"
#include "rss_item.h" #include "rss_item.h"
@ -53,6 +55,7 @@ namespace RSS
namespace Private namespace Private
{ {
class FeedSerializer;
class Parser; class Parser;
struct ParsingResult; struct ParsingResult;
} }
@ -96,13 +99,12 @@ namespace RSS
void handleDownloadFinished(const Net::DownloadResult &result); void handleDownloadFinished(const Net::DownloadResult &result);
void handleParsingFinished(const Private::ParsingResult &result); void handleParsingFinished(const Private::ParsingResult &result);
void handleArticleRead(Article *article); void handleArticleRead(Article *article);
void handleArticleLoadFinished(const QVector<QVariantHash> &articles);
private: private:
void timerEvent(QTimerEvent *event) override; void timerEvent(QTimerEvent *event) override;
void cleanup() override; void cleanup() override;
void load(); void load();
void loadArticles(const QByteArray &data);
void loadArticlesLegacy();
void store(); void store();
void storeDeferred(); void storeDeferred();
bool addArticle(Article *article); bool addArticle(Article *article);
@ -112,14 +114,17 @@ namespace RSS
void downloadIcon(); void downloadIcon();
int updateArticles(const QList<QVariantHash> &loadedArticles); int updateArticles(const QList<QVariantHash> &loadedArticles);
Session *m_session; Session *m_session = nullptr;
Private::Parser *m_parser; Private::Parser *m_parser = nullptr;
Private::FeedSerializer *m_serializer = nullptr;
const QUuid m_uid; const QUuid m_uid;
const QString m_url; const QString m_url;
QString m_title; QString m_title;
QString m_lastBuildDate; QString m_lastBuildDate;
bool m_hasError = false; bool m_hasError = false;
bool m_isLoading = false; bool m_isLoading = false;
bool m_isInitialized = false;
bool m_pendingRefresh = false;
QHash<QString, Article *> m_articles; QHash<QString, Article *> m_articles;
QList<Article *> m_articlesByDate; QList<Article *> m_articlesByDate;
int m_unreadCount = 0; int m_unreadCount = 0;

8
src/base/rss/rss_session.cpp

@ -122,12 +122,14 @@ Session::~Session()
{ {
qDebug() << "Deleting RSS Session..."; qDebug() << "Deleting RSS Session...";
m_workingThread->quit();
m_workingThread->wait();
//store(); //store();
delete m_itemsByPath[u""_qs]; // deleting root folder delete m_itemsByPath[u""_qs]; // deleting root folder
// some items may add I/O operation at destruction
// stop working thread after deleting root folder
m_workingThread->quit();
m_workingThread->wait();
qDebug() << "RSS Session deleted."; qDebug() << "RSS Session deleted.";
} }

Loading…
Cancel
Save