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

2
src/base/base.pri

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

126
src/base/rss/feed_serializer.cpp

@ -0,0 +1,126 @@ @@ -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 @@ @@ -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 @@ @@ -1,6 +1,6 @@
/*
* 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 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -34,12 +34,12 @@ @@ -34,12 +34,12 @@
#include <utility>
#include <vector>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QUrl>
#include <QVector>
#include "base/asyncfilestorage.h"
#include "base/global.h"
@ -47,6 +47,7 @@ @@ -47,6 +47,7 @@
#include "base/net/downloadmanager.h"
#include "base/profile.h"
#include "base/utils/fs.h"
#include "feed_serializer.h"
#include "rss_article.h"
#include "rss_parser.h"
#include "rss_session.h"
@ -79,6 +80,11 @@ Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *s @@ -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_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->moveToThread(m_session->workingThread());
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 @@ -98,6 +104,7 @@ Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *s
Feed::~Feed()
{
store();
emit aboutToBeDestroyed(this);
}
@ -130,6 +137,12 @@ void Feed::markAsRead() @@ -130,6 +137,12 @@ void Feed::markAsRead()
void Feed::refresh()
{
if (!m_isInitialized)
{
m_pendingRefresh = true;
return;
}
if (m_downloadHandler)
m_downloadHandler->cancel();
@ -162,7 +175,7 @@ QString Feed::title() const @@ -162,7 +175,7 @@ QString Feed::title() const
bool Feed::isLoading() const
{
return m_isLoading;
return m_isLoading || !m_isInitialized;
}
QString Feed::lastBuildDate() const
@ -261,100 +274,30 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result) @@ -261,100 +274,30 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
void Feed::load()
{
QFile file {(m_session->dataFileStorage()->storageDir() / m_dataFileName).data()};
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())
, 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()))
QMetaObject::invokeMethod(m_serializer, [this]()
{
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 &) {}
}
m_serializer->load((m_session->dataFileStorage()->storageDir() / m_dataFileName), m_url);
});
}
void Feed::store()
{
if (!m_dirty) return;
if (!m_dirty)
return;
m_dirty = false;
m_savingTimer.stop();
QJsonArray jsonArr;
QVector<QVariantHash> articlesData;
articlesData.reserve(m_articles.size());
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()
@ -546,8 +489,33 @@ void Feed::handleArticleRead(Article *article) @@ -546,8 +489,33 @@ void Feed::handleArticleRead(Article *article)
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()
{
m_dirty = false;
m_savingTimer.stop();
Utils::Fs::removeFile(m_session->dataFileStorage()->storageDir() / m_dataFileName);
Utils::Fs::removeFile(m_iconPath);
}

15
src/base/rss/rss_feed.h

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

8
src/base/rss/rss_session.cpp

@ -122,12 +122,14 @@ Session::~Session() @@ -122,12 +122,14 @@ Session::~Session()
{
qDebug() << "Deleting RSS Session...";
m_workingThread->quit();
m_workingThread->wait();
//store();
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.";
}

Loading…
Cancel
Save