mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-29 13:58:51 +03:00
Implement Import/Export RSS rules in JSON format
This commit is contained in:
parent
b8fc415870
commit
916cfcdb03
4 changed files with 150 additions and 47 deletions
|
@ -38,6 +38,7 @@
|
|||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
|
||||
#include "../bittorrent/magneturi.h"
|
||||
#include "../bittorrent/session.h"
|
||||
|
@ -64,6 +65,32 @@ const QString RulesFileName(QStringLiteral("download_rules.json"));
|
|||
|
||||
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
|
||||
|
||||
namespace
|
||||
{
|
||||
QVector<RSS::AutoDownloadRule> rulesFromJSON(const QByteArray &jsonData)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError)
|
||||
throw RSS::ParsingError(jsonError.errorString());
|
||||
|
||||
if (!jsonDoc.isObject())
|
||||
throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format."));
|
||||
|
||||
const QJsonObject jsonObj {jsonDoc.object()};
|
||||
QVector<RSS::AutoDownloadRule> rules;
|
||||
for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it) {
|
||||
const QJsonValue jsonVal {it.value()};
|
||||
if (!jsonVal.isObject())
|
||||
throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format."));
|
||||
|
||||
rules.append(RSS::AutoDownloadRule::fromJsonObject(jsonVal.toObject(), it.key()));
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace RSS;
|
||||
|
||||
QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr;
|
||||
|
@ -85,8 +112,8 @@ AutoDownloader::AutoDownloader()
|
|||
connect(m_ioThread, &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater);
|
||||
connect(m_fileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
||||
{
|
||||
Logger::instance()->addMessage(QString("Couldn't save RSS AutoDownloader data in %1. Error: %2")
|
||||
.arg(fileName).arg(errorString), Log::WARNING);
|
||||
LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
|
||||
.arg(fileName).arg(errorString), Log::CRITICAL);
|
||||
});
|
||||
|
||||
m_ioThread->start();
|
||||
|
@ -175,6 +202,43 @@ void AutoDownloader::removeRule(const QString &ruleName)
|
|||
}
|
||||
}
|
||||
|
||||
QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) const
|
||||
{
|
||||
switch (format) {
|
||||
case RulesFileFormat::Legacy:
|
||||
return exportRulesToLegacyFormat();
|
||||
default:
|
||||
return exportRulesToJSONFormat();
|
||||
}
|
||||
}
|
||||
|
||||
void AutoDownloader::importRules(const QByteArray &data, AutoDownloader::RulesFileFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case RulesFileFormat::Legacy:
|
||||
importRulesFromLegacyFormat(data);
|
||||
break;
|
||||
default:
|
||||
importRulesFromJSONFormat(data);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray AutoDownloader::exportRulesToJSONFormat() const
|
||||
{
|
||||
QJsonObject jsonObj;
|
||||
for (const auto &rule : rules())
|
||||
jsonObj.insert(rule.name(), rule.toJsonObject());
|
||||
|
||||
return QJsonDocument(jsonObj).toJson();
|
||||
}
|
||||
|
||||
void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data)
|
||||
{
|
||||
const auto rules = rulesFromJSON(data);
|
||||
for (const auto &rule : rules)
|
||||
insertRule(rule);
|
||||
}
|
||||
|
||||
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
|
||||
{
|
||||
QVariantHash dict;
|
||||
|
@ -189,19 +253,17 @@ QByteArray AutoDownloader::exportRulesToLegacyFormat() const
|
|||
return data;
|
||||
}
|
||||
|
||||
bool AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
|
||||
void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
|
||||
{
|
||||
QDataStream in(data);
|
||||
in.setVersion(QDataStream::Qt_4_5);
|
||||
QVariantHash dict;
|
||||
in >> dict;
|
||||
if (in.status() != QDataStream::Ok)
|
||||
return false;
|
||||
throw ParsingError(tr("Invalid data format"));
|
||||
|
||||
for (const QVariant &val : dict)
|
||||
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AutoDownloader::process()
|
||||
|
@ -306,39 +368,20 @@ void AutoDownloader::load()
|
|||
else if (rulesFile.open(QFile::ReadOnly))
|
||||
loadRules(rulesFile.readAll());
|
||||
else
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
|
||||
.arg(rulesFile.fileName()).arg(rulesFile.errorString()), Log::WARNING);
|
||||
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
|
||||
.arg(rulesFile.fileName()).arg(rulesFile.errorString()), Log::CRITICAL);
|
||||
}
|
||||
|
||||
void AutoDownloader::loadRules(const QByteArray &data)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't parse RSS AutoDownloader rules. Error: %1")
|
||||
.arg(jsonError.errorString()), Log::WARNING);
|
||||
return;
|
||||
try {
|
||||
const auto rules = rulesFromJSON(data);
|
||||
for (const auto &rule : rules)
|
||||
setRule_impl(rule);
|
||||
}
|
||||
|
||||
if (!jsonDoc.isObject()) {
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't load RSS AutoDownloader rules. Invalid data format."), Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
foreach (const QString &key, jsonObj.keys()) {
|
||||
const QJsonValue jsonVal = jsonObj.value(key);
|
||||
if (!jsonVal.isObject()) {
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't load RSS AutoDownloader rule '%1'. Invalid data format.")
|
||||
.arg(key), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
setRule_impl(AutoDownloadRule::fromJsonObject(jsonVal.toObject(), key));
|
||||
catch (const ParsingError &error) {
|
||||
LogMsg(tr("Couldn't load RSS AutoDownloader rules. Reason: %1")
|
||||
.arg(error.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,3 +458,13 @@ void AutoDownloader::timerEvent(QTimerEvent *event)
|
|||
Q_UNUSED(event);
|
||||
store();
|
||||
}
|
||||
|
||||
ParsingError::ParsingError(const QString &message)
|
||||
: std::runtime_error(message.toUtf8().data())
|
||||
{
|
||||
}
|
||||
|
||||
QString ParsingError::message() const
|
||||
{
|
||||
return what();
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QBasicTimer>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
|
@ -49,6 +51,13 @@ namespace RSS
|
|||
|
||||
class AutoDownloadRule;
|
||||
|
||||
class ParsingError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
explicit ParsingError(const QString &message);
|
||||
QString message() const;
|
||||
};
|
||||
|
||||
class AutoDownloader final: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -60,6 +69,12 @@ namespace RSS
|
|||
~AutoDownloader() override;
|
||||
|
||||
public:
|
||||
enum class RulesFileFormat
|
||||
{
|
||||
Legacy,
|
||||
JSON
|
||||
};
|
||||
|
||||
static AutoDownloader *instance();
|
||||
|
||||
bool isProcessingEnabled() const;
|
||||
|
@ -73,8 +88,8 @@ namespace RSS
|
|||
bool renameRule(const QString &ruleName, const QString &newRuleName);
|
||||
void removeRule(const QString &ruleName);
|
||||
|
||||
QByteArray exportRulesToLegacyFormat() const;
|
||||
bool importRulesFromLegacyFormat(const QByteArray &data);
|
||||
QByteArray exportRules(RulesFileFormat format = RulesFileFormat::JSON) const;
|
||||
void importRules(const QByteArray &data, RulesFileFormat format = RulesFileFormat::JSON);
|
||||
|
||||
signals:
|
||||
void processingStateChanged(bool enabled);
|
||||
|
@ -101,6 +116,10 @@ namespace RSS
|
|||
void loadRulesLegacy();
|
||||
void store();
|
||||
void storeDeferred();
|
||||
QByteArray exportRulesToJSONFormat() const;
|
||||
void importRulesFromJSONFormat(const QByteArray &data);
|
||||
QByteArray exportRulesToLegacyFormat() const;
|
||||
void importRulesFromLegacyFormat(const QByteArray &data);
|
||||
|
||||
static QPointer<AutoDownloader> m_instance;
|
||||
|
||||
|
|
|
@ -54,8 +54,13 @@
|
|||
#include "autoexpandabledialog.h"
|
||||
#include "ui_automatedrssdownloader.h"
|
||||
|
||||
const QString EXT_JSON {QStringLiteral(".json")};
|
||||
const QString EXT_LEGACY {QStringLiteral(".rssrules")};
|
||||
|
||||
AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_formatFilterJSON(QString("%1 (*%2)").arg(tr("Rules")).arg(EXT_JSON))
|
||||
, m_formatFilterLegacy(QString("%1 (*%2)").arg(tr("Rules (legacy)")).arg(EXT_LEGACY))
|
||||
, m_ui(new Ui::AutomatedRssDownloader)
|
||||
, m_currentRuleItem(nullptr)
|
||||
{
|
||||
|
@ -390,17 +395,30 @@ void AutomatedRssDownloader::on_exportBtn_clicked()
|
|||
return;
|
||||
}
|
||||
|
||||
QString selectedFilter {m_formatFilterJSON};
|
||||
QString path = QFileDialog::getSaveFileName(
|
||||
this, tr("Where would you like to save the list?")
|
||||
, QDir::homePath(), tr("Rules list (legacy)") + QString(" (*.rssrules)"));
|
||||
this, tr("Export RSS rules"), QDir::homePath()
|
||||
, QString("%1;;%2").arg(m_formatFilterJSON).arg(m_formatFilterLegacy), &selectedFilter);
|
||||
if (path.isEmpty()) return;
|
||||
|
||||
if (!path.endsWith(".rssrules", Qt::CaseInsensitive))
|
||||
path += ".rssrules";
|
||||
const RSS::AutoDownloader::RulesFileFormat format {
|
||||
(selectedFilter == m_formatFilterJSON)
|
||||
? RSS::AutoDownloader::RulesFileFormat::JSON
|
||||
: RSS::AutoDownloader::RulesFileFormat::Legacy
|
||||
};
|
||||
|
||||
QFile file(path);
|
||||
if (format == RSS::AutoDownloader::RulesFileFormat::JSON) {
|
||||
if (!path.endsWith(EXT_JSON, Qt::CaseInsensitive))
|
||||
path += EXT_JSON;
|
||||
}
|
||||
else {
|
||||
if (!path.endsWith(EXT_LEGACY, Qt::CaseInsensitive))
|
||||
path += EXT_LEGACY;
|
||||
}
|
||||
|
||||
QFile file {path};
|
||||
if (!file.open(QFile::WriteOnly)
|
||||
|| (file.write(RSS::AutoDownloader::instance()->exportRulesToLegacyFormat()) == -1)) {
|
||||
|| (file.write(RSS::AutoDownloader::instance()->exportRules(format)) == -1)) {
|
||||
QMessageBox::critical(
|
||||
this, tr("I/O Error")
|
||||
, tr("Failed to create the destination file. Reason: %1").arg(file.errorString()));
|
||||
|
@ -409,13 +427,14 @@ void AutomatedRssDownloader::on_exportBtn_clicked()
|
|||
|
||||
void AutomatedRssDownloader::on_importBtn_clicked()
|
||||
{
|
||||
QString selectedFilter {m_formatFilterJSON};
|
||||
QString path = QFileDialog::getOpenFileName(
|
||||
this, tr("Please point to the RSS download rules file")
|
||||
, QDir::homePath(), tr("Rules list (legacy)") + QString(" (*.rssrules)"));
|
||||
this, tr("Import RSS rules"), QDir::homePath()
|
||||
, QString("%1;;%2").arg(m_formatFilterJSON).arg(m_formatFilterLegacy), &selectedFilter);
|
||||
if (path.isEmpty() || !QFile::exists(path))
|
||||
return;
|
||||
|
||||
QFile file(path);
|
||||
QFile file {path};
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::critical(
|
||||
this, tr("I/O Error")
|
||||
|
@ -423,10 +442,19 @@ void AutomatedRssDownloader::on_importBtn_clicked()
|
|||
return;
|
||||
}
|
||||
|
||||
if (!RSS::AutoDownloader::instance()->importRulesFromLegacyFormat(file.readAll())) {
|
||||
const RSS::AutoDownloader::RulesFileFormat format {
|
||||
(selectedFilter == m_formatFilterJSON)
|
||||
? RSS::AutoDownloader::RulesFileFormat::JSON
|
||||
: RSS::AutoDownloader::RulesFileFormat::Legacy
|
||||
};
|
||||
|
||||
try {
|
||||
RSS::AutoDownloader::instance()->importRules(file.readAll(),format);
|
||||
}
|
||||
catch (const RSS::ParsingError &error) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Import Error")
|
||||
, tr("Failed to import the selected rules file."));
|
||||
, tr("Failed to import the selected rules file. Reason: %1").arg(error.message()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,9 @@ private:
|
|||
void updateFeedList();
|
||||
void addFeedArticlesToTree(RSS::Feed *feed, const QStringList &articles);
|
||||
|
||||
const QString m_formatFilterJSON;
|
||||
const QString m_formatFilterLegacy;
|
||||
|
||||
Ui::AutomatedRssDownloader *m_ui;
|
||||
QListWidgetItem *m_currentRuleItem;
|
||||
QShortcut *m_editHotkey;
|
||||
|
|
Loading…
Reference in a new issue