3434#include < QHash>
3535#include < QJsonArray>
3636#include < QJsonObject>
37- #include < QRegExp>
3837#include < QRegularExpression>
3938#include < QSharedData>
4039#include < QString>
4645#include " ../utils/string.h"
4746#include " rss_feed.h"
4847#include " rss_article.h"
48+ #include " rss_autodownloader.h"
4949
5050namespace
5151{
@@ -100,6 +100,8 @@ const QString Str_AssignedCategory(QStringLiteral("assignedCategory"));
100100const QString Str_LastMatch (QStringLiteral(" lastMatch" ));
101101const QString Str_IgnoreDays (QStringLiteral(" ignoreDays" ));
102102const QString Str_AddPaused (QStringLiteral(" addPaused" ));
103+ const QString Str_SmartFilter (QStringLiteral(" smartFilter" ));
104+ const QString Str_PreviouslyMatched (QStringLiteral(" previouslyMatchedEpisodes" ));
103105
104106namespace RSS
105107{
@@ -120,6 +122,10 @@ namespace RSS
120122 QString category;
121123 TriStateBool addPaused = TriStateBool::Undefined;
122124
125+ bool smartFilter = false ;
126+ QStringList previouslyMatchedEpisodes;
127+
128+ mutable QString lastComputedEpisode;
123129 mutable QHash<QString, QRegularExpression> cachedRegexes;
124130
125131 bool operator ==(const AutoDownloadRuleData &other) const
@@ -135,13 +141,38 @@ namespace RSS
135141 && (lastMatch == other.lastMatch )
136142 && (savePath == other.savePath )
137143 && (category == other.category )
138- && (addPaused == other.addPaused );
144+ && (addPaused == other.addPaused )
145+ && (smartFilter == other.smartFilter );
139146 }
140147 };
141148}
142149
143150using namespace RSS ;
144151
152+ QString computeEpisodeName (const QString &article)
153+ {
154+ const QRegularExpression episodeRegex = AutoDownloader::instance ()->smartEpisodeRegex ();
155+ const QRegularExpressionMatch match = episodeRegex.match (article);
156+
157+ // See if we can extract an season/episode number or date from the title
158+ if (!match.hasMatch ())
159+ return QString ();
160+
161+ QStringList ret;
162+ for (int i = 1 ; i <= match.lastCapturedIndex (); ++i) {
163+ QString cap = match.captured (i);
164+
165+ if (cap.isEmpty ())
166+ continue ;
167+
168+ bool isInt = false ;
169+ int x = cap.toInt (&isInt);
170+
171+ ret.append (isInt ? QString::number (x) : cap);
172+ }
173+ return ret.join (' x' );
174+ }
175+
145176AutoDownloadRule::AutoDownloadRule (const QString &name)
146177 : m_dataPtr(new AutoDownloadRuleData)
147178{
@@ -197,6 +228,9 @@ bool AutoDownloadRule::matches(const QString &articleTitle, const QString &expre
197228
198229bool AutoDownloadRule::matches (const QString &articleTitle) const
199230{
231+ // Reset the lastComputedEpisode, we don't want to leak it between matches
232+ m_dataPtr->lastComputedEpisode .clear ();
233+
200234 if (!m_dataPtr->mustContain .empty ()) {
201235 bool logged = false ;
202236 bool foundMustContain = false ;
@@ -334,6 +368,20 @@ bool AutoDownloadRule::matches(const QString &articleTitle) const
334368 return false ;
335369 }
336370
371+ if (useSmartFilter ()) {
372+ // now see if this episode has been downloaded before
373+ const QString episodeStr = computeEpisodeName (articleTitle);
374+
375+ if (!episodeStr.isEmpty ()) {
376+ bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes .contains (episodeStr);
377+ bool isRepack = articleTitle.contains (" REPACK" , Qt::CaseInsensitive) || articleTitle.contains (" PROPER" , Qt::CaseInsensitive);
378+ if (previouslyMatched && !isRepack)
379+ return false ;
380+
381+ m_dataPtr->lastComputedEpisode = episodeStr;
382+ }
383+ }
384+
337385// qDebug() << "Matched article:" << articleTitle;
338386 return true ;
339387}
@@ -367,7 +415,9 @@ QJsonObject AutoDownloadRule::toJsonObject() const
367415 , {Str_AssignedCategory, assignedCategory ()}
368416 , {Str_LastMatch, lastMatch ().toString (Qt::RFC2822Date)}
369417 , {Str_IgnoreDays, ignoreDays ()}
370- , {Str_AddPaused, triStateBoolToJsonValue (addPaused ())}};
418+ , {Str_AddPaused, triStateBoolToJsonValue (addPaused ())}
419+ , {Str_SmartFilter, useSmartFilter ()}
420+ , {Str_PreviouslyMatched, QJsonArray::fromStringList (previouslyMatchedEpisodes ())}};
371421}
372422
373423AutoDownloadRule AutoDownloadRule::fromJsonObject (const QJsonObject &jsonObj, const QString &name)
@@ -384,6 +434,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
384434 rule.setAddPaused (jsonValueToTriStateBool (jsonObj.value (Str_AddPaused)));
385435 rule.setLastMatch (QDateTime::fromString (jsonObj.value (Str_LastMatch).toString (), Qt::RFC2822Date));
386436 rule.setIgnoreDays (jsonObj.value (Str_IgnoreDays).toInt ());
437+ rule.setUseSmartFilter (jsonObj.value (Str_SmartFilter).toBool (false ));
387438
388439 const QJsonValue feedsVal = jsonObj.value (Str_AffectedFeeds);
389440 QStringList feedURLs;
@@ -393,6 +444,17 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
393444 feedURLs << urlVal.toString ();
394445 rule.setFeedURLs (feedURLs);
395446
447+ const QJsonValue previouslyMatchedVal = jsonObj.value (Str_PreviouslyMatched);
448+ QStringList previouslyMatched;
449+ if (previouslyMatchedVal.isString ()) {
450+ previouslyMatched << previouslyMatchedVal.toString ();
451+ }
452+ else {
453+ foreach (const QJsonValue &val, previouslyMatchedVal.toArray ())
454+ previouslyMatched << val.toString ();
455+ }
456+ rule.setPreviouslyMatchedEpisodes (previouslyMatched);
457+
396458 return rule;
397459}
398460
@@ -549,6 +611,16 @@ QString AutoDownloadRule::mustNotContain() const
549611 return m_dataPtr->mustNotContain .join (" |" );
550612}
551613
614+ bool AutoDownloadRule::useSmartFilter () const
615+ {
616+ return m_dataPtr->smartFilter ;
617+ }
618+
619+ void AutoDownloadRule::setUseSmartFilter (bool enabled)
620+ {
621+ m_dataPtr->smartFilter = enabled;
622+ }
623+
552624bool AutoDownloadRule::useRegex () const
553625{
554626 return m_dataPtr->useRegex ;
@@ -560,6 +632,25 @@ void AutoDownloadRule::setUseRegex(bool enabled)
560632 m_dataPtr->cachedRegexes .clear ();
561633}
562634
635+ QStringList AutoDownloadRule::previouslyMatchedEpisodes () const
636+ {
637+ return m_dataPtr->previouslyMatchedEpisodes ;
638+ }
639+
640+ void AutoDownloadRule::setPreviouslyMatchedEpisodes (const QStringList &previouslyMatchedEpisodes)
641+ {
642+ m_dataPtr->previouslyMatchedEpisodes = previouslyMatchedEpisodes;
643+ }
644+
645+ void AutoDownloadRule::appendLastComputedEpisode ()
646+ {
647+ if (!m_dataPtr->lastComputedEpisode .isEmpty ()) {
648+ // TODO: probably need to add a marker for PROPER/REPACK to avoid duplicate downloads
649+ m_dataPtr->previouslyMatchedEpisodes .append (m_dataPtr->lastComputedEpisode );
650+ m_dataPtr->lastComputedEpisode .clear ();
651+ }
652+ }
653+
563654QString AutoDownloadRule::episodeFilter () const
564655{
565656 return m_dataPtr->episodeFilter ;
0 commit comments