1
0
mirror of https://github.com/d47081/qBittorrent.git synced 2025-02-06 03:44:29 +00:00

Made smart episode filter regular expression configurable

This commit is contained in:
Stephen Dawkins 2018-01-27 13:40:00 +00:00
parent 2845a791d0
commit 48cbccff1e
6 changed files with 87 additions and 34 deletions

View File

@ -64,6 +64,7 @@ const QString ConfFolderName(QStringLiteral("rss"));
const QString RulesFileName(QStringLiteral("download_rules.json")); const QString RulesFileName(QStringLiteral("download_rules.json"));
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing")); const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
const QString SettingsKey_SmartEpisodeFilter(QStringLiteral("RSS/AutoDownloader/SmartEpisodeFilter"));
namespace namespace
{ {
@ -95,6 +96,11 @@ using namespace RSS;
QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr; QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr;
QString computeSmartFilterRegex(const QStringList &filters)
{
return QString("(?:_|\\b)(?:%1)(?:_|\\b)").arg(filters.join(QString(")|(?:")));
}
AutoDownloader::AutoDownloader() AutoDownloader::AutoDownloader()
: m_processingEnabled(SettingsStorage::instance()->loadValue(SettingsKey_ProcessingEnabled, false).toBool()) : m_processingEnabled(SettingsStorage::instance()->loadValue(SettingsKey_ProcessingEnabled, false).toBool())
, m_processingTimer(new QTimer(this)) , m_processingTimer(new QTimer(this))
@ -123,6 +129,13 @@ AutoDownloader::AutoDownloader()
connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFailed connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFailed
, this, &AutoDownloader::handleTorrentDownloadFailed); , this, &AutoDownloader::handleTorrentDownloadFailed);
// initialise the smart episode regex
const QString regex = computeSmartFilterRegex(smartEpisodeFilters());
m_smartEpisodeRegex = QRegularExpression(regex,
QRegularExpression::CaseInsensitiveOption
| QRegularExpression::ExtendedPatternSyntaxOption
| QRegularExpression::UseUnicodePropertiesOption);
load(); load();
m_processingTimer->setSingleShot(true); m_processingTimer->setSingleShot(true);
@ -266,6 +279,37 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash())); insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
} }
QStringList AutoDownloader::smartEpisodeFilters() const
{
const QVariant filtersSetting = SettingsStorage::instance()->loadValue(SettingsKey_SmartEpisodeFilter);
if (filtersSetting.isNull()) {
QStringList filters = {
"s(\\d+)e(\\d+)", // Format 1: s01e01
"(\\d+)x(\\d+)", // Format 2: 01x01
"(\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})", // Format 3: 2017.01.01
"(\\d{1,2}[.\\-]\\d{1,2}[.\\-]\\d{4})" // Format 4: 01.01.2017
};
return filters;
}
return filtersSetting.toStringList();
}
QRegularExpression AutoDownloader::smartEpisodeRegex() const
{
return m_smartEpisodeRegex;
}
void AutoDownloader::setSmartEpisodeFilters(const QStringList &filters)
{
SettingsStorage::instance()->storeValue(SettingsKey_SmartEpisodeFilter, filters);
const QString regex = computeSmartFilterRegex(filters);
m_smartEpisodeRegex.setPattern(regex);
}
void AutoDownloader::process() void AutoDownloader::process()
{ {
if (m_processingQueue.isEmpty()) return; // processing was disabled if (m_processingQueue.isEmpty()) return; // processing was disabled

View File

@ -35,6 +35,7 @@
#include <QList> #include <QList>
#include <QObject> #include <QObject>
#include <QPointer> #include <QPointer>
#include <QRegularExpression>
#include <QSharedPointer> #include <QSharedPointer>
class QThread; class QThread;
@ -80,6 +81,10 @@ namespace RSS
bool isProcessingEnabled() const; bool isProcessingEnabled() const;
void setProcessingEnabled(bool enabled); void setProcessingEnabled(bool enabled);
QStringList smartEpisodeFilters() const;
void setSmartEpisodeFilters(const QStringList &filters);
QRegularExpression smartEpisodeRegex() const;
bool hasRule(const QString &ruleName) const; bool hasRule(const QString &ruleName) const;
AutoDownloadRule ruleByName(const QString &ruleName) const; AutoDownloadRule ruleByName(const QString &ruleName) const;
QList<AutoDownloadRule> rules() const; QList<AutoDownloadRule> rules() const;
@ -132,5 +137,6 @@ namespace RSS
QHash<QString, QSharedPointer<ProcessingJob>> m_waitingJobs; QHash<QString, QSharedPointer<ProcessingJob>> m_waitingJobs;
bool m_dirty = false; bool m_dirty = false;
QBasicTimer m_savingTimer; QBasicTimer m_savingTimer;
QRegularExpression m_smartEpisodeRegex;
}; };
} }

View File

@ -45,6 +45,7 @@
#include "../utils/string.h" #include "../utils/string.h"
#include "rss_feed.h" #include "rss_feed.h"
#include "rss_article.h" #include "rss_article.h"
#include "rss_autodownloader.h"
namespace namespace
{ {
@ -150,40 +151,26 @@ using namespace RSS;
QString computeEpisodeName(const QString &article) QString computeEpisodeName(const QString &article)
{ {
const QRegularExpression episodeRegex( const QRegularExpression episodeRegex = AutoDownloader::instance()->smartEpisodeRegex();
"(?:^|[^a-z0-9])(?:" const QRegularExpressionMatch match = episodeRegex.match(article);
//Format 1: s01e01
"(?:s(\\d+)e(\\d+))|"
//Format 2: 01x01
"(?:(\\d+)x(\\d+))|"
//Format 3: 2017.01.01
"((?:\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})|"
//Format 4: 01.01.2017
"(?:\\d{1,2}[.\\-]\\d{1,2}[.\\-]\\d{4}))"
")(?:[^a-z0-9]|$)",
QRegularExpression::CaseInsensitiveOption
| QRegularExpression::ExtendedPatternSyntaxOption);
QRegularExpressionMatch match = episodeRegex.match(article);
// See if we can extract an season/episode number or date from the title // See if we can extract an season/episode number or date from the title
if (!match.hasMatch()) { if (!match.hasMatch())
return QString(); return QString();
}
int lastCapturedIndex = match.lastCapturedIndex(); QStringList ret;
if (lastCapturedIndex == 5) { for (int i = 1; i <= match.lastCapturedIndex(); ++i) {
return match.captured(5); QString cap = match.captured(i);
}
else { if (cap.isEmpty())
return QString("%1x%2").arg(match.captured(lastCapturedIndex - 1).toInt()) continue;
.arg(match.captured(lastCapturedIndex).toInt());
bool isInt = false;
int x = cap.toInt(&isInt);
ret.append(isInt ? QString::number(x) : cap);
} }
return ret.join('x');
} }
AutoDownloadRule::AutoDownloadRule(const QString &name) AutoDownloadRule::AutoDownloadRule(const QString &name)
@ -383,14 +370,14 @@ bool AutoDownloadRule::matches(const QString &articleTitle) const
if (useSmartFilter()) { if (useSmartFilter()) {
// now see if this episode has been downloaded before // now see if this episode has been downloaded before
QString episodeStr = computeEpisodeName(articleTitle); const QString episodeStr = computeEpisodeName(articleTitle);
if (!episodeStr.isEmpty()) { if (!episodeStr.isEmpty()) {
bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes.contains(episodeStr); bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes.contains(episodeStr);
bool isRepack = articleTitle.contains("REPACK", Qt::CaseInsensitive) || articleTitle.contains("PROPER", Qt::CaseInsensitive); bool isRepack = articleTitle.contains("REPACK", Qt::CaseInsensitive) || articleTitle.contains("PROPER", Qt::CaseInsensitive);
if (previouslyMatched && !isRepack) { if (previouslyMatched && !isRepack)
return false; return false;
}
m_dataPtr->lastComputedEpisode = episodeStr; m_dataPtr->lastComputedEpisode = episodeStr;
} }
} }

View File

@ -372,6 +372,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
// RSS tab // RSS tab
connect(m_ui->checkRSSEnable, &QCheckBox::toggled, this, &OptionsDialog::enableApplyButton); connect(m_ui->checkRSSEnable, &QCheckBox::toggled, this, &OptionsDialog::enableApplyButton);
connect(m_ui->checkRSSAutoDownloaderEnable, &QCheckBox::toggled, this, &OptionsDialog::enableApplyButton); connect(m_ui->checkRSSAutoDownloaderEnable, &QCheckBox::toggled, this, &OptionsDialog::enableApplyButton);
connect(m_ui->textSmartEpisodeFilters, &QPlainTextEdit::textChanged, this, &OptionsDialog::enableApplyButton);
connect(m_ui->spinRSSRefreshInterval, qSpinBoxValueChanged, this, &OptionsDialog::enableApplyButton); connect(m_ui->spinRSSRefreshInterval, qSpinBoxValueChanged, this, &OptionsDialog::enableApplyButton);
connect(m_ui->spinRSSMaxArticlesPerFeed, qSpinBoxValueChanged, this, &OptionsDialog::enableApplyButton); connect(m_ui->spinRSSMaxArticlesPerFeed, qSpinBoxValueChanged, this, &OptionsDialog::enableApplyButton);
connect(m_ui->btnEditRules, &QPushButton::clicked, [this]() { AutomatedRssDownloader(this).exec(); }); connect(m_ui->btnEditRules, &QPushButton::clicked, [this]() { AutomatedRssDownloader(this).exec(); });
@ -548,6 +549,7 @@ void OptionsDialog::saveOptions()
RSS::Session::instance()->setMaxArticlesPerFeed(m_ui->spinRSSMaxArticlesPerFeed->value()); RSS::Session::instance()->setMaxArticlesPerFeed(m_ui->spinRSSMaxArticlesPerFeed->value());
RSS::Session::instance()->setProcessingEnabled(m_ui->checkRSSEnable->isChecked()); RSS::Session::instance()->setProcessingEnabled(m_ui->checkRSSEnable->isChecked());
RSS::AutoDownloader::instance()->setProcessingEnabled(m_ui->checkRSSAutoDownloaderEnable->isChecked()); RSS::AutoDownloader::instance()->setProcessingEnabled(m_ui->checkRSSAutoDownloaderEnable->isChecked());
RSS::AutoDownloader::instance()->setSmartEpisodeFilters(m_ui->textSmartEpisodeFilters->toPlainText().split('\n', QString::SplitBehavior::SkipEmptyParts));
auto session = BitTorrent::Session::instance(); auto session = BitTorrent::Session::instance();
@ -772,6 +774,8 @@ void OptionsDialog::loadOptions()
m_ui->checkRSSEnable->setChecked(RSS::Session::instance()->isProcessingEnabled()); m_ui->checkRSSEnable->setChecked(RSS::Session::instance()->isProcessingEnabled());
m_ui->checkRSSAutoDownloaderEnable->setChecked(RSS::AutoDownloader::instance()->isProcessingEnabled()); m_ui->checkRSSAutoDownloaderEnable->setChecked(RSS::AutoDownloader::instance()->isProcessingEnabled());
m_ui->textSmartEpisodeFilters->setPlainText(RSS::AutoDownloader::instance()->smartEpisodeFilters().join('\n'));
m_ui->spinRSSRefreshInterval->setValue(RSS::Session::instance()->refreshInterval()); m_ui->spinRSSRefreshInterval->setValue(RSS::Session::instance()->refreshInterval());
m_ui->spinRSSMaxArticlesPerFeed->setValue(RSS::Session::instance()->maxArticlesPerFeed()); m_ui->spinRSSMaxArticlesPerFeed->setValue(RSS::Session::instance()->maxArticlesPerFeed());

View File

@ -2699,6 +2699,18 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupRSSSmartEpisodeFilter">
<property name="title">
<string>RSS Smart Episode Filters</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_31">
<item>
<widget class="QPlainTextEdit" name="textSmartEpisodeFilters"/>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer_5"> <spacer name="verticalSpacer_5">
<property name="orientation"> <property name="orientation">
@ -2707,7 +2719,7 @@
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>20</width>
<height>267</height> <height>200</height>
</size> </size>
</property> </property>
</spacer> </spacer>

View File

@ -532,7 +532,7 @@ void AutomatedRssDownloader::handleRuleCheckStateChange(QListWidgetItem *ruleIte
void AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList() void AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList()
{ {
QMessageBox::StandardButton reply = QMessageBox::question( const QMessageBox::StandardButton reply = QMessageBox::question(
this, this,
tr("Clear downloaded episodes"), tr("Clear downloaded episodes"),
tr("Are you sure you want to clear the list of downloaded episodes for the selected rule?"), tr("Are you sure you want to clear the list of downloaded episodes for the selected rule?"),