diff --git a/src/base/rss/rss_autodownloader.cpp b/src/base/rss/rss_autodownloader.cpp index 8edfe2e70..438509bc8 100644 --- a/src/base/rss/rss_autodownloader.cpp +++ b/src/base/rss/rss_autodownloader.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #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 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 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::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; - } - - if (!jsonDoc.isObject()) { - Logger::instance()->addMessage( - QString("Couldn't load RSS AutoDownloader rules. Invalid data format."), Log::WARNING); - return; + try { + const auto rules = rulesFromJSON(data); + for (const auto &rule : rules) + setRule_impl(rule); } - - 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(); +} diff --git a/src/base/rss/rss_autodownloader.h b/src/base/rss/rss_autodownloader.h index 26947678b..ba9c0ac39 100644 --- a/src/base/rss/rss_autodownloader.h +++ b/src/base/rss/rss_autodownloader.h @@ -28,6 +28,8 @@ #pragma once +#include + #include #include #include @@ -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 m_instance; diff --git a/src/gui/rss/automatedrssdownloader.cpp b/src/gui/rss/automatedrssdownloader.cpp index 9f1995c86..dc2cf0aa6 100644 --- a/src/gui/rss/automatedrssdownloader.cpp +++ b/src/gui/rss/automatedrssdownloader.cpp @@ -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())); } } diff --git a/src/gui/rss/automatedrssdownloader.h b/src/gui/rss/automatedrssdownloader.h index acbd9b955..d336fbc60 100644 --- a/src/gui/rss/automatedrssdownloader.h +++ b/src/gui/rss/automatedrssdownloader.h @@ -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;