[feature] Indexer Test status filter (#11705) Partial solution for #3292

This commit is contained in:
Alessio Gogna 2021-05-15 23:52:12 +02:00 committed by GitHub
parent 51f7d6c306
commit 6dab689533
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 170 additions and 39 deletions

View File

@ -586,25 +586,26 @@ To get all Jackett indexers including their capabilities you can use `t=indexers
### Filter indexers
Another special "filter" indexer is avaible at <code>/api/v2.0/indexers/<i><b>filter</b></i>/results/torznab</code>
It will query the configured indexers that match the <i><b>filter</b></i> expression criterias and return the combined results as "all".
Another special "filter" indexer is available at `/api/v2.0/indexers/<filter>/results/torznab`
It will query the configured indexers that match the `<filter>` expression criterias and return the combined results as "all".
Supported filters
Filter | Condition
-|-
<code>type:<i><b>type</b></i></code> | where the indexer type is equal to <i><b>type</b></i>
<code>tag:<i><b>tag</b></i></code> | where the indexer tags contains <i><b>tag</b></i>
<code>lang:<i><b>tag</b></i></code> | where the indexer language start with <i><b>lang</b></i>
`type:<type>` | where the indexer type is equal to `<type>`
`tag:<tag>` | where the indexer tags contains `<tag>`
`lang:<tag>` | where the indexer language start with `<lang>`
`test:{passed|failed}` | where the last indexer test performed `passed` or `failed`
Supported operators
Operator | Condition
-|-
<code>!<i><b>filter</b></i></code> | where not <i><b>filter</b></i>
<code><i><b>filter1</b></i>+<i><b>filter2</b></i>+...</code> | where <i><b>filter1</b></i> and <i><b>filter2</b> and ...</
<code><i><b>filter1</b></i>,<i><b>filter2</b></i>,...</code> | where <i><b>filter1</b></i> or <i><b>filter2</b> or ...</
`!<expr>` | where not `<expr>`
`<expr1>+<expr2>[+<expr3>...]` | where `<expr1>` and `<expr2>` [and `<expr3>`...]
`<expr1>,<expr2>[+<expr3>...]` | where `<expr1>` or `<expr2>` [or `<expr3>`...]
Example:
The "filter" indexer at <code>/api/v2.0/indexers/<b>tag:group1,!type:private+lang:en</b>/results/torznab</code> will query all the configured indexers tagged with `group1` or all the indexers not private and with `en` language (`en-en`,`en-us`,...)
The "filter" indexer at `/api/v2.0/indexers/tag:group1,!type:private+lang:en/results/torznab` will query all the configured indexers tagged with `group1` or all the indexers not private and with `en` language (`en-en`,`en-us`,...)
## Installation on Windows
We recommend you install Jackett as a Windows service using the supplied installer. You may also download the zipped version if you would like to configure everything manually.

View File

@ -84,6 +84,10 @@ function tag_filter(indexer) {
return indexer.tags.map(t => t.toLowerCase()).indexOf(this.value.toLowerCase()) > -1;
}
function state_filter(indexer) {
return indexer.state == this.value;
}
function getJackettConfig(callback) {
api.getServerConfig(callback).fail(function () {
doNotify("Error loading Jackett settings, request to Jackett server failed, is server running ?", "danger", "glyphicon glyphicon-alert");
@ -211,6 +215,9 @@ function configureFilters(indexers) {
availableFilters.push(f);
}
availableFilters.push({id: "test:passed", apply: state_filter, value: "success" });
availableFilters.push({id: "test:failed", apply: state_filter, value: "error" });
["public", "private", "semi-private"]
.map(t => { return { id: "type:" + t, apply: type_filter, value: t } })
.forEach(add);
@ -606,6 +613,10 @@ function updateTestState(id, state, message, parent) {
}).rows().invalidate('dom');
if (state != "inprogres")
dt.draw();
var indexer = configuredIndexers.find(x => x.id == id);
if (indexer)
indexer.state = state;
}
function testIndexer(id, notifyResult) {

View File

@ -16,10 +16,11 @@ namespace Jackett.Common.Utils
});
public static readonly FilterFuncComponent Language = Component("lang", args => indexer => indexer.Language.StartsWith(args, StringComparison.InvariantCultureIgnoreCase));
public static readonly FilterFuncComponent Type = Component("type", args => indexer => string.Equals(indexer.Type, args, StringComparison.InvariantCultureIgnoreCase));
public static readonly FilterFuncComponent Test = TestFilterFunc.Default;
static FilterFunc()
{
Expression = new FilterFuncExpression(Tag, Language, Type);
Expression = new FilterFuncExpression(Tag, Language, Type, Test);
}
public static bool TryParse(string source, out Func<IIndexer, bool> func)
@ -49,10 +50,10 @@ namespace Jackett.Common.Utils
public override Func<IIndexer, bool> ToFunc(string args)
{
var func = builder(args);
return indexer => indexer != null
? indexer.IsConfigured && func(indexer)
: throw new ArgumentNullException(nameof(indexer));
return indexer => IsValid(indexer) && func(indexer);
}
}
protected static bool IsValid(IIndexer indexer) => (indexer?.IsConfigured ?? throw new ArgumentNullException(nameof(indexer)));
}
}

View File

@ -0,0 +1,28 @@
using System;
using Jackett.Common.Indexers;
namespace Jackett.Common.Utils.FilterFuncs
{
public class TestFilterFunc : FilterFuncComponent
{
public static readonly TestFilterFunc Default = new TestFilterFunc();
public const string Passed = "passed";
public const string Failed = "failed";
private TestFilterFunc() : base("test")
{
}
public override Func<IIndexer, bool> ToFunc(string args)
{
if (args == null)
throw new ArgumentNullException(nameof(args));
if (string.Equals(Passed, args, StringComparison.InvariantCultureIgnoreCase))
return i => IsValid(i) && i.LastError == null;
if (string.Equals(Failed, args, StringComparison.InvariantCultureIgnoreCase))
return i => IsValid(i) && i.LastError != null;
throw new ArgumentException($"Invalid filter. Status should be '{Passed}' or '{Failed}'", nameof(args));
}
}
}

View File

@ -8,7 +8,7 @@ using Newtonsoft.Json.Linq;
namespace Jackett.Test.Common.Utils.FilterFuncs
{
public class IndexerStub : IIndexer
public abstract class IndexerBaseStub : IIndexer
{
public virtual string SiteLink => throw TestExceptions.UnexpectedInvocation;

View File

@ -6,9 +6,9 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
[TestFixture]
public class LanguageFuncTests
{
private class LanguageIndexerStub : IndexerStub
private class IndexerStub : IndexerBaseStub
{
public LanguageIndexerStub(string language)
public IndexerStub(string language)
{
Language = language;
}
@ -24,19 +24,19 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
var language = "en";
var region = "US";
var lrLanguage = new LanguageIndexerStub($"{language.ToLower()}-{region.ToLower()}");
var lrLanguage = new IndexerStub(language: $"{language.ToLower()}-{region.ToLower()}");
var LRFilterFunc = Language.ToFunc($"{language.ToUpper()}-{region.ToUpper()}");
Assert.IsTrue(LRFilterFunc(lrLanguage));
var lRLanguage = new LanguageIndexerStub($"{language.ToLower()}-{region.ToUpper()}");
var lRLanguage = new IndexerStub(language: $"{language.ToLower()}-{region.ToUpper()}");
var LrFilterFunc = Language.ToFunc($"{language.ToUpper()}-{region.ToLower()}");
Assert.IsTrue(LrFilterFunc(lRLanguage));
var LrLanguage = new LanguageIndexerStub($"{language.ToUpper()}-{region.ToLower()}");
var LrLanguage = new IndexerStub(language: $"{language.ToUpper()}-{region.ToLower()}");
var lRFilterFunc = Language.ToFunc($"{language.ToLower()}-{region.ToUpper()}");
Assert.IsTrue(lRFilterFunc(LrLanguage));
var LRLanguage = new LanguageIndexerStub($"{language.ToUpper()}-{region.ToUpper()}");
var LRLanguage = new IndexerStub(language: $"{language.ToUpper()}-{region.ToUpper()}");
var lrFilterFunc = Language.ToFunc($"{language.ToLower()}-{region.ToLower()}");
Assert.IsTrue(lrFilterFunc(LRLanguage));
}
@ -47,9 +47,9 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
var language = "en";
var funcFilter = Language.ToFunc(language);
Assert.IsTrue(funcFilter(new LanguageIndexerStub(language)));
Assert.IsTrue(funcFilter(new LanguageIndexerStub($"{language}-region1")));
Assert.IsFalse(funcFilter(new LanguageIndexerStub($"language2-{language}")));
Assert.IsTrue(funcFilter(new IndexerStub(language: language)));
Assert.IsTrue(funcFilter(new IndexerStub(language: $"{language}-region1")));
Assert.IsFalse(funcFilter(new IndexerStub(language: $"language2-{language}")));
}
}
}

View File

@ -6,9 +6,9 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
[TestFixture]
public class TagFuncTests
{
private class TagsIndexerStub : IndexerStub
private class IndexerStub : IndexerBaseStub
{
public TagsIndexerStub(params string[] tags)
public IndexerStub(params string[] tags)
{
Tags = tags;
}
@ -23,7 +23,7 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
{
var tagId = "g1";
var tag = new TagsIndexerStub(tagId);
var tag = new IndexerStub(tagId);
var upperTarget = Tag.ToFunc(tagId.ToUpper());
Assert.IsTrue(upperTarget(tag));
@ -38,10 +38,10 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
var tagId = "g1";
var target = Tag.ToFunc(tagId);
Assert.IsTrue(target(new TagsIndexerStub(tagId)));
Assert.IsTrue(target(new TagsIndexerStub(tagId, "g2")));
Assert.IsTrue(target(new TagsIndexerStub("g2", tagId)));
Assert.IsFalse(target(new TagsIndexerStub("g2")));
Assert.IsTrue(target(new IndexerStub(tagId)));
Assert.IsTrue(target(new IndexerStub(tagId, "g2")));
Assert.IsTrue(target(new IndexerStub("g2", tagId)));
Assert.IsFalse(target(new IndexerStub("g2")));
}
}
}

View File

@ -0,0 +1,91 @@
using System;
using Jackett.Common.Utils;
using Jackett.Common.Utils.FilterFuncs;
using Jackett.Test.TestHelpers;
using NUnit.Framework;
namespace Jackett.Test.Common.Utils.FilterFuncs
{
[TestFixture]
public class TestFuncTests
{
private static readonly IndexerStub HealthyIndexer = new IndexerStub(lastError: null);
private static readonly IndexerStub ErrorIndexer = new IndexerStub(lastError: "error");
private class IndexerStub : IndexerBaseStub
{
private readonly string lastError;
public IndexerStub(string lastError)
{
this.lastError = lastError;
}
public override bool IsConfigured => true;
public override string LastError
{
get => lastError;
set => throw TestExceptions.UnexpectedInvocation;
}
}
[Test]
public void NullStatus_ThrowsException()
{
Assert.Throws<ArgumentNullException>(() => FilterFunc.Test.ToFunc(null));
}
[Test]
public void EmptyStatus_ThrowsException()
{
Assert.Throws<ArgumentException>(() => FilterFunc.Test.ToFunc(string.Empty));
}
[Test]
public void InvalidStatus_ThrowsException()
{
Assert.Throws<ArgumentException>(() => FilterFunc.Test.ToFunc(TestFilterFunc.Passed + TestFilterFunc.Failed));
}
[Test]
public void PassedFilter()
{
var passedFilterFunc = FilterFunc.Test.ToFunc(TestFilterFunc.Passed);
Assert.IsTrue(passedFilterFunc(HealthyIndexer));
Assert.IsFalse(passedFilterFunc(ErrorIndexer));
}
[Test]
public void FailedFilter()
{
var failedFilterFunc = FilterFunc.Test.ToFunc(TestFilterFunc.Failed);
Assert.IsFalse(failedFilterFunc(HealthyIndexer));
Assert.IsTrue(failedFilterFunc(ErrorIndexer));
}
[Test]
public void PassedFilter_CaseInsensitiveSource()
{
var upperFilterFunc = FilterFunc.Test.ToFunc(TestFilterFunc.Passed.ToUpper());
Assert.IsTrue(upperFilterFunc(HealthyIndexer));
Assert.IsFalse(upperFilterFunc(ErrorIndexer));
var lowerFilterFunc = FilterFunc.Test.ToFunc(TestFilterFunc.Passed.ToLower());
Assert.IsTrue(lowerFilterFunc(HealthyIndexer));
Assert.IsFalse(lowerFilterFunc(ErrorIndexer));
}
[Test]
public void FailedFilter_CaseInsensitiveSource()
{
var upperFilterFunc = FilterFunc.Test.ToFunc(TestFilterFunc.Failed.ToUpper());
Assert.IsFalse(upperFilterFunc(HealthyIndexer));
Assert.IsTrue(upperFilterFunc(ErrorIndexer));
var lowerFilterFunc = FilterFunc.Test.ToFunc(TestFilterFunc.Failed.ToLower());
Assert.IsFalse(lowerFilterFunc(HealthyIndexer));
Assert.IsTrue(lowerFilterFunc(ErrorIndexer));
}
}
}

View File

@ -1,4 +1,5 @@
using NUnit.Framework;
using static Jackett.Common.Utils.FilterFunc;
namespace Jackett.Test.Common.Utils.FilterFuncs
@ -6,9 +7,9 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
[TestFixture]
public class TypeFuncTests
{
private class TypeIndexerStub : IndexerStub
private class IndexerStub : IndexerBaseStub
{
public TypeIndexerStub(string type)
public IndexerStub(string type)
{
Type = type;
}
@ -23,8 +24,8 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
{
var typeId = "type-id";
var lowerType = new TypeIndexerStub(typeId.ToLower());
var upperType = new TypeIndexerStub(typeId.ToUpper());
var lowerType = new IndexerStub(type: typeId.ToLower());
var upperType = new IndexerStub(type: typeId.ToUpper());
var upperFilterFunc = Type.ToFunc(typeId.ToUpper());
Assert.IsTrue(upperFilterFunc(lowerType));
@ -42,11 +43,9 @@ namespace Jackett.Test.Common.Utils.FilterFuncs
var funcFilter = Type.ToFunc($"{typeId}");
Assert.IsFalse(funcFilter(new TypeIndexerStub($"{typeId}suffix")));
Assert.IsFalse(funcFilter(new TypeIndexerStub($"prefix{typeId}")));
Assert.IsFalse(funcFilter(new TypeIndexerStub($"prefix{typeId}suffix")));
Assert.IsFalse(funcFilter(new IndexerStub(type: $"{typeId}suffix")));
Assert.IsFalse(funcFilter(new IndexerStub(type: $"prefix{typeId}")));
Assert.IsFalse(funcFilter(new IndexerStub(type: $"prefix{typeId}suffix")));
}
}
}