mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-22 09:16:05 +03:00
Merge pull request #800 from Gelmir/smart_ep
Implement episode filter for rss downloader
This commit is contained in:
commit
9088b1af84
5 changed files with 127 additions and 0 deletions
|
@ -68,6 +68,21 @@ AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer<RssManager>& m
|
|||
Q_ASSERT(ok);
|
||||
m_ruleList = manager.toStrongRef()->downloadRules();
|
||||
m_editableRuleList = new RssDownloadRuleList; // Read rule list from disk
|
||||
m_episodeValidator = new QRegExpValidator(
|
||||
QRegExp("^(^[1-9]{1,1}\\d{0,3}x([1-9]{1,1}\\d{0,3}(-([1-9]{1,1}\\d{0,3})?)?;){1,}){1,1}",
|
||||
Qt::CaseInsensitive),
|
||||
ui->lineEFilter);
|
||||
ui->lineEFilter->setValidator(m_episodeValidator);
|
||||
QString tip = "<p>" + tr("Matches articles based on episode filter.") + "</p><p><b>" + tr("Example: ") +
|
||||
"1x2;8-15;5;30-;</b>" + tr(" will match 2, 5, 8 through 15, 30 and onward episodes of season one", "example X will match") + "</p>";
|
||||
tip += "<p>" + tr("Episode filter rules: ") + "</p><ul><li>" + tr("Season number is a mandatory non-zero value") + "</li>" +
|
||||
"<li>" + tr("Episode number is a mandatory non-zero value") + "</li>" +
|
||||
"<li>" + tr("Filter must end with semicolon") + "</li>" +
|
||||
"<li>" + tr("Three range types for episodes are supported: ") + "</li>" + "<li><ul>"
|
||||
"<li>" + tr("Single number: <b>1x25;</b> matches episode 25 of season one") + "</li>" +
|
||||
"<li>" + tr("Normal range: <b>1x25-40;</b> matches episodes 25 through 40 of season one") + "</li>" +
|
||||
"<li>" + tr("Infinite range: <b>1x25-;</b> matches 40 and onward episodes of season one") + "</li>" + "</ul></li></ul>";
|
||||
ui->lineEFilter->setToolTip(tip);
|
||||
initLabelCombobox();
|
||||
loadFeedList();
|
||||
loadSettings();
|
||||
|
@ -94,6 +109,8 @@ AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer<RssManager>& m
|
|||
Q_ASSERT(ok);
|
||||
ok = connect(this, SIGNAL(finished(int)), SLOT(on_finished(int)));
|
||||
Q_ASSERT(ok);
|
||||
ok = connect(ui->lineEFilter, SIGNAL(textEdited(QString)), SLOT(updateMatchingArticles()));
|
||||
Q_ASSERT(ok);
|
||||
editHotkey = new QShortcut(QKeySequence("F2"), ui->listRules, 0, 0, Qt::WidgetShortcut);
|
||||
ok = connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedRule()));
|
||||
Q_ASSERT(ok);
|
||||
|
@ -113,6 +130,7 @@ AutomatedRssDownloader::~AutomatedRssDownloader()
|
|||
delete deleteHotkey;
|
||||
delete ui;
|
||||
delete m_editableRuleList;
|
||||
delete m_episodeValidator;
|
||||
}
|
||||
|
||||
void AutomatedRssDownloader::loadSettings()
|
||||
|
@ -223,6 +241,11 @@ void AutomatedRssDownloader::updateRuleDefinitionBox()
|
|||
if (rule) {
|
||||
ui->lineContains->setText(rule->mustContain());
|
||||
ui->lineNotContains->setText(rule->mustNotContain());
|
||||
QString ep = rule->episodeFilter();
|
||||
if (!ep.isEmpty())
|
||||
ui->lineEFilter->setText(ep);
|
||||
else
|
||||
ui->lineEFilter->clear();
|
||||
ui->saveDiffDir_check->setChecked(!rule->savePath().isEmpty());
|
||||
ui->lineSavePath->setText(fsutils::toNativePath(rule->savePath()));
|
||||
ui->checkRegex->setChecked(rule->useRegex());
|
||||
|
@ -301,6 +324,7 @@ void AutomatedRssDownloader::saveEditedRule()
|
|||
rule->setUseRegex(ui->checkRegex->isChecked());
|
||||
rule->setMustContain(ui->lineContains->text());
|
||||
rule->setMustNotContain(ui->lineNotContains->text());
|
||||
rule->setEpisodeFilter(ui->lineEFilter->text());
|
||||
if (ui->saveDiffDir_check->isChecked())
|
||||
rule->setSavePath(ui->lineSavePath->text());
|
||||
else
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <QDialog>
|
||||
#include <QWeakPointer>
|
||||
#include <QShortcut>
|
||||
#include <QRegExpValidator>
|
||||
#include "rssdownloadrule.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
@ -94,6 +95,7 @@ private:
|
|||
QListWidgetItem* m_editedRule;
|
||||
RssDownloadRuleList *m_ruleList;
|
||||
RssDownloadRuleList *m_editableRuleList;
|
||||
QRegExpValidator *m_episodeValidator;
|
||||
QShortcut *editHotkey;
|
||||
QShortcut *deleteHotkey;
|
||||
};
|
||||
|
|
|
@ -216,6 +216,45 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEFilter"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblEFilterStat">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>18</width>
|
||||
<height>18</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>18</width>
|
||||
<height>18</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="lblEFilter">
|
||||
<property name="text">
|
||||
<string>Episode filter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
|
|
@ -60,6 +60,63 @@ bool RssDownloadRule::matches(const QString &article_title) const
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (!m_episodeFilter.isEmpty()) {
|
||||
qDebug("Checking episode filter");
|
||||
QRegExp f("(^\\d{1,4})x(.*;$)");
|
||||
int pos = f.indexIn(m_episodeFilter);
|
||||
if (pos < 0)
|
||||
return false;
|
||||
|
||||
QString s = f.cap(1);
|
||||
QStringList eps = f.cap(2).split(";");
|
||||
QString expStr;
|
||||
expStr += "s0?" + s + "[ -_\.]?" + "e0?";
|
||||
|
||||
foreach (const QString& ep, eps) {
|
||||
if (ep.isEmpty())
|
||||
continue;
|
||||
|
||||
if (ep.indexOf('-') != -1) { // Range detected
|
||||
QString partialPattern = "s0?" + s + "[ -_\.]?" + "e(0?\\d{1,4})";
|
||||
QRegExp reg(partialPattern, Qt::CaseInsensitive);
|
||||
|
||||
if (ep.endsWith('-')) { // Infinite range
|
||||
int epOurs = ep.left(ep.size() - 1).toInt();
|
||||
|
||||
// Extract partial match from article and ocmpare as digits
|
||||
pos = reg.indexIn(article_title);
|
||||
if (pos != -1) {
|
||||
int epTheirs = reg.cap(1).toInt();
|
||||
if (epTheirs >= epOurs)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else { // Normal range
|
||||
QStringList range = ep.split('-');
|
||||
Q_ASSERT(range.size() == 2);
|
||||
if (range.first().toInt() > range.last().toInt())
|
||||
continue; // Ignore this subrule completely
|
||||
|
||||
int epOursFirst = range.first().toInt();
|
||||
int epOursLast = range.last().toInt();
|
||||
|
||||
// Extract partial match from article and ocmpare as digits
|
||||
pos = reg.indexIn(article_title);
|
||||
if (pos != -1) {
|
||||
int epTheirs = reg.cap(1).toInt();
|
||||
if (epOursFirst <= epTheirs && epOursLast >= epTheirs)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Single number
|
||||
QRegExp reg(expStr + ep + "\\D", Qt::CaseInsensitive);
|
||||
if (reg.indexIn(article_title) != -1)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -86,6 +143,7 @@ RssDownloadRulePtr RssDownloadRule::fromVariantHash(const QVariantHash &rule_has
|
|||
rule->setUseRegex(rule_hash.value("use_regex", false).toBool());
|
||||
rule->setMustContain(rule_hash.value("must_contain").toString());
|
||||
rule->setMustNotContain(rule_hash.value("must_not_contain").toString());
|
||||
rule->setEpisodeFilter(rule_hash.value("episode_filter").toString());
|
||||
rule->setRssFeeds(rule_hash.value("affected_feeds").toStringList());
|
||||
rule->setEnabled(rule_hash.value("enabled", false).toBool());
|
||||
rule->setSavePath(rule_hash.value("save_path").toString());
|
||||
|
@ -104,6 +162,7 @@ QVariantHash RssDownloadRule::toVariantHash() const
|
|||
hash["enabled"] = m_enabled;
|
||||
hash["label_assigned"] = m_label;
|
||||
hash["use_regex"] = m_useRegex;
|
||||
hash["episode_filter"] = m_episodeFilter;
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,8 @@ public:
|
|||
inline QString mustNotContain() const { return m_mustNotContain.join(" "); }
|
||||
inline bool useRegex() const { return m_useRegex; }
|
||||
inline void setUseRegex(bool enabled) { m_useRegex = enabled; }
|
||||
inline QString episodeFilter() const { return m_episodeFilter; }
|
||||
inline void setEpisodeFilter(const QString& e) { m_episodeFilter = e; }
|
||||
QStringList findMatchingArticles(const RssFeedPtr& feed) const;
|
||||
// Operators
|
||||
bool operator==(const RssDownloadRule &other) const;
|
||||
|
@ -73,6 +75,7 @@ private:
|
|||
QString m_name;
|
||||
QStringList m_mustContain;
|
||||
QStringList m_mustNotContain;
|
||||
QString m_episodeFilter;
|
||||
QString m_savePath;
|
||||
QString m_label;
|
||||
bool m_enabled;
|
||||
|
|
Loading…
Reference in a new issue