From a5e8af50702fde21999345eeb1150fccc802433f Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 5 Jun 2023 14:55:41 +0300 Subject: [PATCH] Allow to assign priority to RSS download rule PR #19000. --- src/base/rss/rss_autodownloader.cpp | 77 ++++++++++++++++++++------ src/base/rss/rss_autodownloader.h | 6 +- src/base/rss/rss_autodownloadrule.cpp | 18 +++++- src/base/rss/rss_autodownloadrule.h | 3 + src/gui/mainwindow.cpp | 1 - src/gui/rss/automatedrssdownloader.cpp | 13 ++++- src/gui/rss/automatedrssdownloader.ui | 33 ++++++++++- src/webui/api/rsscontroller.cpp | 2 +- 8 files changed, 124 insertions(+), 29 deletions(-) diff --git a/src/base/rss/rss_autodownloader.cpp b/src/base/rss/rss_autodownloader.cpp index 582390daf..bf7072fd8 100644 --- a/src/base/rss/rss_autodownloader.cpp +++ b/src/base/rss/rss_autodownloader.cpp @@ -28,6 +28,8 @@ #include "rss_autodownloader.h" +#include + #include #include #include @@ -166,25 +168,27 @@ AutoDownloader *AutoDownloader::instance() bool AutoDownloader::hasRule(const QString &ruleName) const { - return m_rules.contains(ruleName); + return m_rulesByName.contains(ruleName); } AutoDownloadRule AutoDownloader::ruleByName(const QString &ruleName) const { - return m_rules.value(ruleName, AutoDownloadRule(u"Unknown Rule"_qs)); + const auto index = m_rulesByName.value(ruleName, -1); + return m_rules.value(index, AutoDownloadRule(u"Unknown Rule"_qs)); } QList AutoDownloader::rules() const { - return m_rules.values(); + return m_rules; } -void AutoDownloader::insertRule(const AutoDownloadRule &rule) +void AutoDownloader::setRule(const AutoDownloadRule &rule) { if (!hasRule(rule.name())) { // Insert new rule setRule_impl(rule); + sortRules(); m_dirty = true; store(); emit ruleAdded(rule.name()); @@ -194,6 +198,7 @@ void AutoDownloader::insertRule(const AutoDownloadRule &rule) { // Update existing rule setRule_impl(rule); + sortRules(); m_dirty = true; storeDeferred(); emit ruleChanged(rule.name()); @@ -203,12 +208,12 @@ void AutoDownloader::insertRule(const AutoDownloadRule &rule) bool AutoDownloader::renameRule(const QString &ruleName, const QString &newRuleName) { - if (!hasRule(ruleName)) return false; - if (hasRule(newRuleName)) return false; + if (!hasRule(ruleName) || hasRule(newRuleName)) + return false; - AutoDownloadRule rule = m_rules.take(ruleName); - rule.setName(newRuleName); - m_rules.insert(newRuleName, rule); + const auto index = m_rulesByName.take(ruleName); + m_rules[index].setName(newRuleName); + m_rulesByName.insert(newRuleName, index); m_dirty = true; store(); emit ruleRenamed(newRuleName, ruleName); @@ -217,13 +222,21 @@ bool AutoDownloader::renameRule(const QString &ruleName, const QString &newRuleN void AutoDownloader::removeRule(const QString &ruleName) { - if (m_rules.contains(ruleName)) + if (!hasRule(ruleName)) + return; + + emit ruleAboutToBeRemoved(ruleName); + + const auto index = m_rulesByName.take(ruleName); + m_rules.removeAt(index); + for (qsizetype i = index; i < m_rules.size(); ++i) { - emit ruleAboutToBeRemoved(ruleName); - m_rules.remove(ruleName); - m_dirty = true; - store(); + const AutoDownloadRule &rule = m_rules[i]; + m_rulesByName[rule.name()] = i; } + + m_dirty = true; + store(); } QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) const @@ -261,7 +274,7 @@ QByteArray AutoDownloader::exportRulesToJSONFormat() const void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data) { for (const auto &rule : asConst(rulesFromJSON(data))) - insertRule(rule); + setRule(rule); } QByteArray AutoDownloader::exportRulesToLegacyFormat() const @@ -288,7 +301,7 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data) throw ParsingError(tr("Invalid data format")); for (const QVariant &val : asConst(dict)) - insertRule(AutoDownloadRule::fromLegacyDict(val.toHash())); + setRule(AutoDownloadRule::fromLegacyDict(val.toHash())); } QStringList AutoDownloader::smartEpisodeFilters() const @@ -399,7 +412,31 @@ void AutoDownloader::handleFeedURLChanged(Feed *feed, const QString &oldURL) void AutoDownloader::setRule_impl(const AutoDownloadRule &rule) { - m_rules.insert(rule.name(), rule); + const QString ruleName = rule.name(); + const auto index = m_rulesByName.value(ruleName, -1); + if (index < 0) + { + m_rules.append(rule); + m_rulesByName[ruleName] = m_rules.size() - 1; + } + else + { + m_rules[index] = rule; + } +} + +void AutoDownloader::sortRules() +{ + std::sort(m_rules.begin(), m_rules.end(), [](const AutoDownloadRule &lhs, const AutoDownloadRule &rhs) + { + return (lhs.priority() < rhs.priority()); + }); + + for (qsizetype i = 0; i < m_rules.size(); ++i) + { + const AutoDownloadRule &rule = m_rules[i]; + m_rulesByName[rule.name()] = i; + } } void AutoDownloader::addJobForArticle(const Article *article) @@ -430,6 +467,9 @@ void AutoDownloader::processJob(const QSharedPointer &job) m_dirty = true; storeDeferred(); + LogMsg(tr("RSS article '%1' is accepted by rule '%2'. Trying to add torrent...") + .arg(job->articleData.value(Article::KeyTitle).toString(), rule.name())); + const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString(); BitTorrent::Session::instance()->addTorrent(torrentURL, rule.addTorrentParams()); @@ -477,6 +517,7 @@ void AutoDownloader::loadRules(const QByteArray &data) const auto rules = rulesFromJSON(data); for (const auto &rule : rules) setRule_impl(rule); + sortRules(); } catch (const ParsingError &error) { @@ -493,7 +534,7 @@ void AutoDownloader::loadRulesLegacy() { const auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash()); if (!rule.name().isEmpty()) - insertRule(rule); + setRule(rule); } } diff --git a/src/base/rss/rss_autodownloader.h b/src/base/rss/rss_autodownloader.h index da4f5e3b7..b00d9d58f 100644 --- a/src/base/rss/rss_autodownloader.h +++ b/src/base/rss/rss_autodownloader.h @@ -94,7 +94,7 @@ namespace RSS AutoDownloadRule ruleByName(const QString &ruleName) const; QList rules() const; - void insertRule(const AutoDownloadRule &rule); + void setRule(const AutoDownloadRule &rule); bool renameRule(const QString &ruleName, const QString &newRuleName); void removeRule(const QString &ruleName); @@ -118,6 +118,7 @@ namespace RSS private: void timerEvent(QTimerEvent *event) override; void setRule_impl(const AutoDownloadRule &rule); + void sortRules(); void resetProcessingQueue(); void startProcessing(); void addJobForArticle(const Article *article); @@ -141,7 +142,8 @@ namespace RSS QTimer *m_processingTimer = nullptr; Utils::Thread::UniquePtr m_ioThread; AsyncFileStorage *m_fileStorage = nullptr; - QHash m_rules; + QList m_rules; + QHash m_rulesByName; QList> m_processingQueue; QHash> m_waitingJobs; bool m_dirty = false; diff --git a/src/base/rss/rss_autodownloadrule.cpp b/src/base/rss/rss_autodownloadrule.cpp index 476f0fcb3..6c5481305 100644 --- a/src/base/rss/rss_autodownloadrule.cpp +++ b/src/base/rss/rss_autodownloadrule.cpp @@ -103,6 +103,7 @@ namespace const QString S_NAME = u"name"_qs; const QString S_ENABLED = u"enabled"_qs; +const QString S_PRIORITY = u"priority"_qs; const QString S_USE_REGEX = u"useRegex"_qs; const QString S_MUST_CONTAIN = u"mustContain"_qs; const QString S_MUST_NOT_CONTAIN = u"mustNotContain"_qs; @@ -126,6 +127,7 @@ namespace RSS { QString name; bool enabled = true; + int priority = 0; QStringList mustContain; QStringList mustNotContain; @@ -147,6 +149,7 @@ namespace RSS { return (left.name == right.name) && (left.enabled == right.enabled) + && (left.priority == right.priority) && (left.mustContain == right.mustContain) && (left.mustNotContain == right.mustNotContain) && (left.episodeFilter == right.episodeFilter) @@ -457,6 +460,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const const BitTorrent::AddTorrentParams &addTorrentParams = m_dataPtr->addTorrentParams; return {{S_ENABLED, isEnabled()} + , {S_PRIORITY, priority()} , {S_USE_REGEX, useRegex()} , {S_MUST_CONTAIN, mustContain()} , {S_MUST_NOT_CONTAIN, mustNotContain()} @@ -483,11 +487,13 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co { AutoDownloadRule rule {(name.isEmpty() ? jsonObj.value(S_NAME).toString() : name)}; + rule.setEnabled(jsonObj.value(S_ENABLED).toBool(true)); + rule.setPriority(jsonObj.value(S_PRIORITY).toInt(0)); + rule.setUseRegex(jsonObj.value(S_USE_REGEX).toBool(false)); rule.setMustContain(jsonObj.value(S_MUST_CONTAIN).toString()); rule.setMustNotContain(jsonObj.value(S_MUST_NOT_CONTAIN).toString()); rule.setEpisodeFilter(jsonObj.value(S_EPISODE_FILTER).toString()); - rule.setEnabled(jsonObj.value(S_ENABLED).toBool(true)); rule.setLastMatch(QDateTime::fromString(jsonObj.value(S_LAST_MATCH).toString(), Qt::RFC2822Date)); rule.setIgnoreDays(jsonObj.value(S_IGNORE_DAYS).toInt()); rule.setUseSmartFilter(jsonObj.value(S_SMART_FILTER).toBool(false)); @@ -665,6 +671,16 @@ void AutoDownloadRule::setEnabled(const bool enable) m_dataPtr->enabled = enable; } +int AutoDownloadRule::priority() const +{ + return m_dataPtr->priority; +} + +void AutoDownloadRule::setPriority(const int value) +{ + m_dataPtr->priority = value; +} + QDateTime AutoDownloadRule::lastMatch() const { return m_dataPtr->lastMatch; diff --git a/src/base/rss/rss_autodownloadrule.h b/src/base/rss/rss_autodownloadrule.h index 92050468b..9e5449bc6 100644 --- a/src/base/rss/rss_autodownloadrule.h +++ b/src/base/rss/rss_autodownloadrule.h @@ -61,6 +61,9 @@ namespace RSS bool isEnabled() const; void setEnabled(bool enable); + int priority() const; + void setPriority(int value); + QString mustContain() const; void setMustContain(const QString &tokens); QString mustNotContain() const; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 59640cc13..9deed2281 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -223,7 +223,6 @@ MainWindow::MainWindow(IGUIApplication *app, WindowState initialState) // Transfer List tab m_transferListWidget = new TransferListWidget(hSplitter, this); - // m_transferListWidget->setStyleSheet("QTreeView {border: none;}"); // borderless m_propertiesWidget = new PropertiesWidget(hSplitter); connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos); hSplitter->addWidget(m_transferListWidget); diff --git a/src/gui/rss/automatedrssdownloader.cpp b/src/gui/rss/automatedrssdownloader.cpp index 9f35afd6b..3f8b25066 100644 --- a/src/gui/rss/automatedrssdownloader.cpp +++ b/src/gui/rss/automatedrssdownloader.cpp @@ -78,6 +78,9 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent) m_ui->setupUi(this); m_ui->torrentParametersGroupBox->layout()->addWidget(m_addTorrentParamsWidget); + m_ui->prioritySpinBox->setMinimum(std::numeric_limits::min()); + m_ui->prioritySpinBox->setMaximum(std::numeric_limits::max()); + connect(m_ui->addRuleBtn, &QPushButton::clicked, this, &AutomatedRssDownloader::onAddRuleBtnClicked); connect(m_ui->removeRuleBtn, &QPushButton::clicked, this, &AutomatedRssDownloader::onRemoveRuleBtnClicked); connect(m_ui->exportBtn, &QPushButton::clicked, this, &AutomatedRssDownloader::onExportBtnClicked); @@ -281,6 +284,8 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() { m_currentRule = RSS::AutoDownloader::instance()->ruleByName(m_currentRuleItem->text()); + m_ui->prioritySpinBox->setValue(m_currentRule.priority()); + m_addTorrentParamsWidget->setAddTorrentParams(m_currentRule.addTorrentParams()); m_ui->lineContains->setText(m_currentRule.mustContain()); @@ -324,6 +329,7 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() void AutomatedRssDownloader::clearRuleDefinitionBox() { m_addTorrentParamsWidget->setAddTorrentParams({}); + m_ui->prioritySpinBox->setValue(0); m_ui->lineContains->clear(); m_ui->lineNotContains->clear(); m_ui->lineEFilter->clear(); @@ -342,6 +348,7 @@ void AutomatedRssDownloader::updateEditedRule() return; m_currentRule.setEnabled(m_currentRuleItem->checkState() != Qt::Unchecked); + m_currentRule.setPriority(m_ui->prioritySpinBox->value()); m_currentRule.setUseRegex(m_ui->checkRegex->isChecked()); m_currentRule.setUseSmartFilter(m_ui->checkSmart->isChecked()); m_currentRule.setMustContain(m_ui->lineContains->text()); @@ -357,7 +364,7 @@ void AutomatedRssDownloader::saveEditedRule() if (!m_currentRuleItem || !m_ui->ruleScrollArea->isEnabled()) return; updateEditedRule(); - RSS::AutoDownloader::instance()->insertRule(m_currentRule); + RSS::AutoDownloader::instance()->setRule(m_currentRule); } void AutomatedRssDownloader::onAddRuleBtnClicked() @@ -377,7 +384,7 @@ void AutomatedRssDownloader::onAddRuleBtnClicked() return; } - RSS::AutoDownloader::instance()->insertRule(RSS::AutoDownloadRule(ruleName)); + RSS::AutoDownloader::instance()->setRule(RSS::AutoDownloadRule(ruleName)); } void AutomatedRssDownloader::onRemoveRuleBtnClicked() @@ -580,7 +587,7 @@ void AutomatedRssDownloader::handleFeedCheckStateChange(QListWidgetItem *feedIte rule.setFeedURLs(affectedFeeds); if (ruleItem != m_currentRuleItem) - RSS::AutoDownloader::instance()->insertRule(rule); + RSS::AutoDownloader::instance()->setRule(rule); else m_currentRule = rule; } diff --git a/src/gui/rss/automatedrssdownloader.ui b/src/gui/rss/automatedrssdownloader.ui index aa0792237..d03177968 100644 --- a/src/gui/rss/automatedrssdownloader.ui +++ b/src/gui/rss/automatedrssdownloader.ui @@ -134,11 +134,38 @@ 0 0 - 329 - 243 + 370 + 277 - + + + + + + + Priority: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/webui/api/rsscontroller.cpp b/src/webui/api/rsscontroller.cpp index 0ed2b35e3..ff86b85cf 100644 --- a/src/webui/api/rsscontroller.cpp +++ b/src/webui/api/rsscontroller.cpp @@ -150,7 +150,7 @@ void RSSController::setRuleAction() const QByteArray ruleDef {params()[u"ruleDef"_qs].trimmed().toUtf8()}; const auto jsonObj = QJsonDocument::fromJson(ruleDef).object(); - RSS::AutoDownloader::instance()->insertRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName)); + RSS::AutoDownloader::instance()->setRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName)); } void RSSController::renameRuleAction()