Browse Source

Made smart episode filter regular expression configurable

adaptive-webui-19844
Stephen Dawkins 7 years ago
parent
commit
48cbccff1e
  1. 44
      src/base/rss/rss_autodownloader.cpp
  2. 6
      src/base/rss/rss_autodownloader.h
  3. 49
      src/base/rss/rss_autodownloadrule.cpp
  4. 4
      src/gui/optionsdlg.cpp
  5. 14
      src/gui/optionsdlg.ui
  6. 2
      src/gui/rss/automatedrssdownloader.cpp

44
src/base/rss/rss_autodownloader.cpp

@ -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

6
src/base/rss/rss_autodownloader.h

@ -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;
}; };
} }

49
src/base/rss/rss_autodownloadrule.cpp

@ -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 // See if we can extract an season/episode number or date from the title
"(?:s(\\d+)e(\\d+))|" if (!match.hasMatch())
return QString();
//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]|$)", QStringList ret;
QRegularExpression::CaseInsensitiveOption for (int i = 1; i <= match.lastCapturedIndex(); ++i) {
| QRegularExpression::ExtendedPatternSyntaxOption); QString cap = match.captured(i);
QRegularExpressionMatch match = episodeRegex.match(article); if (cap.isEmpty())
continue;
// See if we can extract an season/episode number or date from the title bool isInt = false;
if (!match.hasMatch()) { int x = cap.toInt(&isInt);
return QString();
}
int lastCapturedIndex = match.lastCapturedIndex(); ret.append(isInt ? QString::number(x) : cap);
if (lastCapturedIndex == 5) {
return match.captured(5);
}
else {
return QString("%1x%2").arg(match.captured(lastCapturedIndex - 1).toInt())
.arg(match.captured(lastCapturedIndex).toInt());
} }
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;
} }
} }

4
src/gui/optionsdlg.cpp

@ -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());

14
src/gui/optionsdlg.ui

@ -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>

2
src/gui/rss/automatedrssdownloader.cpp

@ -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?"),

Loading…
Cancel
Save