core: refactor book-search (#9897)

This commit is contained in:
Diego Heras
2020-10-19 21:26:43 +02:00
committed by GitHub
parent 92ab804cbc
commit da0761406f
5 changed files with 396 additions and 50 deletions

View File

@@ -18,7 +18,7 @@ caps:
modes:
search: [q]
book-search: [q, author, title]
book-search: [q, title, author]
settings:
- name: username

View File

@@ -111,16 +111,8 @@ namespace Jackett.Common.Indexers
DefaultSiteLink += "/";
Language = Definition.Language;
Type = Definition.Type;
TorznabCaps = new TorznabCapabilities
{
BookSearchAvailable = Definition.Caps.Modes.Any(c => c.Key == "book-search" && c.Value.Contains("author") && c.Value.Contains("title"))
};
if (Definition.Caps.Modes.ContainsKey("tv-search"))
TorznabCaps.ParseTvSearchParams(Definition.Caps.Modes["tv-search"]);
if (Definition.Caps.Modes.ContainsKey("movie-search"))
TorznabCaps.ParseMovieSearchParams(Definition.Caps.Modes["movie-search"]);
if (Definition.Caps.Modes.ContainsKey("music-search"))
TorznabCaps.ParseMusicSearchParams(Definition.Caps.Modes["music-search"]);
TorznabCaps = new TorznabCapabilities();
TorznabCaps.ParseCardigannSearchModes(Definition.Caps.Modes);
// init config Data
configData = new ConfigurationData();

View File

@@ -31,6 +31,13 @@ namespace Jackett.Common.Models
Year
}
public enum BookSearchParam
{
Q,
Title,
Author
}
public class TorznabCapabilities
{
public int? LimitsMax { get; set; }
@@ -59,9 +66,12 @@ namespace Jackett.Common.Models
public bool MusicSearchLabelAvailable => (MusicSearchParams.Contains(MusicSearchParam.Label));
public bool MusicSearchYearAvailable => (MusicSearchParams.Contains(MusicSearchParam.Year));
public bool BookSearchAvailable { get; set; }
public List<BookSearchParam> BookSearchParams;
public bool BookSearchAvailable => (BookSearchParams.Count > 0);
public bool BookSearchTitleAvailable => (BookSearchParams.Contains(BookSearchParam.Title));
public bool BookSearchAuthorAvailable => (BookSearchParams.Contains(BookSearchParam.Author));
public List<TorznabCategory> Categories { get; private set; }
public List<TorznabCategory> Categories { get; set; }
public TorznabCapabilities()
{
@@ -69,11 +79,41 @@ namespace Jackett.Common.Models
TvSearchParams = new List<TvSearchParam>();
MovieSearchParams = new List<MovieSearchParam>();
MusicSearchParams = new List<MusicSearchParam>();
BookSearchAvailable = false;
BookSearchParams = new List<BookSearchParam>();
Categories = new List<TorznabCategory>();
}
public void ParseTvSearchParams(IEnumerable<string> paramsList)
public void ParseCardigannSearchModes(Dictionary<string,List<string>> modes)
{
if (modes == null || !modes.Any())
throw new Exception("At least one search mode is required");
if (!modes.ContainsKey("search"))
throw new Exception("The search mode 'search' is mandatory");
foreach (var entry in modes)
switch (entry.Key)
{
case "search":
if (entry.Value == null || entry.Value.Count != 1 || entry.Value[0] != "q")
throw new Exception("In search mode 'search' only 'q' parameter is supported and it's mandatory");
break;
case "tv-search":
ParseTvSearchParams(entry.Value);
break;
case "movie-search":
ParseMovieSearchParams(entry.Value);
break;
case "music-search":
ParseMusicSearchParams(entry.Value);
break;
case "book-search":
ParseBookSearchParams(entry.Value);
break;
default:
throw new Exception($"Unsupported search mode: {entry.Key}");
}
}
private void ParseTvSearchParams(IEnumerable<string> paramsList)
{
if (paramsList == null)
return;
@@ -87,7 +127,7 @@ namespace Jackett.Common.Models
throw new Exception($"Not supported tv-search param: {paramStr}");
}
public void ParseMovieSearchParams(IEnumerable<string> paramsList)
private void ParseMovieSearchParams(IEnumerable<string> paramsList)
{
if (paramsList == null)
return;
@@ -101,7 +141,7 @@ namespace Jackett.Common.Models
throw new Exception($"Not supported movie-search param: {paramStr}");
}
public void ParseMusicSearchParams(IEnumerable<string> paramsList)
private void ParseMusicSearchParams(IEnumerable<string> paramsList)
{
if (paramsList == null)
return;
@@ -112,7 +152,21 @@ namespace Jackett.Common.Models
else
throw new Exception($"Duplicate music-search param: {paramStr}");
else
throw new Exception($"Not supported Music-search param: {paramStr}");
throw new Exception($"Not supported music-search param: {paramStr}");
}
private void ParseBookSearchParams(IEnumerable<string> paramsList)
{
if (paramsList == null)
return;
foreach (var paramStr in paramsList)
if (Enum.TryParse(paramStr, true, out BookSearchParam param))
if (!BookSearchParams.Contains(param))
BookSearchParams.Add(param);
else
throw new Exception($"Duplicate book-search param: {paramStr}");
else
throw new Exception($"Not supported book-search param: {paramStr}");
}
private string SupportedTvSearchParams()
@@ -155,16 +209,15 @@ namespace Jackett.Common.Models
return string.Join(",", parameters);
}
private string SupportedBookSearchParams
private string SupportedBookSearchParams()
{
get
{
var parameters = new List<string>() { "q" };
if (BookSearchAvailable)
parameters.Add("author,title");
var parameters = new List<string> { "q" }; // q is always enabled
if (BookSearchTitleAvailable)
parameters.Add("title");
if (BookSearchAuthorAvailable)
parameters.Add("author");
return string.Join(",", parameters);
}
}
public bool SupportsCategories(int[] categories)
{
@@ -212,7 +265,7 @@ namespace Jackett.Common.Models
),
new XElement("book-search",
new XAttribute("available", BookSearchAvailable ? "yes" : "no"),
new XAttribute("supportedParams", SupportedBookSearchParams)
new XAttribute("supportedParams", SupportedBookSearchParams())
)
),
new XElement("categories",
@@ -241,7 +294,7 @@ namespace Jackett.Common.Models
lhs.TvSearchParams = lhs.TvSearchParams.Union(rhs.TvSearchParams).ToList();
lhs.MovieSearchParams = lhs.MovieSearchParams.Union(rhs.MovieSearchParams).ToList();
lhs.MusicSearchParams = lhs.MusicSearchParams.Union(rhs.MusicSearchParams).ToList();
lhs.BookSearchAvailable = lhs.BookSearchAvailable || rhs.BookSearchAvailable;
lhs.BookSearchParams = lhs.BookSearchParams.Union(rhs.BookSearchParams).ToList();
lhs.Categories.AddRange(rhs.Categories.Where(x => x.ID < 100000).Except(lhs.Categories)); // exclude indexer specific categories (>= 100000)
return lhs;
}

View File

@@ -36,29 +36,47 @@ namespace Jackett.Test.Common.Models
Assert.False(torznabCaps.MusicSearchLabelAvailable);
Assert.False(torznabCaps.MusicSearchYearAvailable);
Assert.IsEmpty(torznabCaps.BookSearchParams);
Assert.False(torznabCaps.BookSearchAvailable);
Assert.False(torznabCaps.BookSearchTitleAvailable);
Assert.False(torznabCaps.BookSearchAuthorAvailable);
Assert.IsEmpty(torznabCaps.Categories);
}
[Test]
public void TestParseMovieSearchParams()
public void TestParseCardigannSearchModes()
{
var torznabCaps = new TorznabCapabilities();
torznabCaps.ParseMovieSearchParams(null);
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string> {"q"}},
{"tv-search", new List<string> {"q"}},
{"movie-search", new List<string> {"q"}},
{"music-search", new List<string> {"q"}},
{"book-search", new List<string> {"q"}}
});
Assert.True(torznabCaps.SearchAvailable);
Assert.True(torznabCaps.TvSearchAvailable);
Assert.True(torznabCaps.MovieSearchAvailable);
Assert.True(torznabCaps.MusicSearchAvailable);
Assert.True(torznabCaps.BookSearchAvailable);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseMovieSearchParams(new List<string>());
Assert.IsEmpty(torznabCaps.MovieSearchParams);
try
{
torznabCaps.ParseCardigannSearchModes(null); // null search modes
Assert.Fail();
}
catch (Exception)
{
// ignored
}
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseMovieSearchParams(new List<string> {"q", "imdbid"});
Assert.AreEqual(new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId }, torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseMovieSearchParams(new List<string> {"q", "q"}); // duplicate param
try
{
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>()); // empty search modes
Assert.Fail();
}
catch (Exception)
@@ -68,7 +86,247 @@ namespace Jackett.Test.Common.Models
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseMovieSearchParams(new List<string> {"bad"}); // unsupported param
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"bad", new List<string> {"q"}} // bad search mode
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string> {"bad"}} // search mode with bad parameters
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
}
[Test]
public void TestParseTvSearchParams()
{
var torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"tv-search", null}
});
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"tv-search", new List<string>()}
});
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"tv-search", new List<string> {"q", "tvdbid"}}
});
Assert.AreEqual(new List<TvSearchParam> { TvSearchParam.Q, TvSearchParam.TvdbId }, torznabCaps.TvSearchParams);
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"tv-search", new List<string> {"q", "q"}} // duplicate param
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"tv-search", new List<string> {"bad"}} // unsupported param
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
}
[Test]
public void TestParseMovieSearchParams()
{
var torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"movie-search", null}
});
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"movie-search", new List<string>()}
});
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"movie-search", new List<string> {"q", "imdbid"}}
});
Assert.AreEqual(new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId }, torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"movie-search", new List<string> {"q", "q"}} // duplicate param
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"movie-search", new List<string> {"bad"}} // unsupported param
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
}
[Test]
public void TestParseMusicSearchParams()
{
var torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"music-search", null}
});
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"music-search", new List<string>()}
});
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"music-search", new List<string> {"q", "label"}}
});
Assert.AreEqual(new List<MusicSearchParam> { MusicSearchParam.Q, MusicSearchParam.Label }, torznabCaps.MusicSearchParams);
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"music-search", new List<string> {"q", "q"}} // duplicate param
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"music-search", new List<string> {"bad"}} // unsupported param
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
}
[Test]
public void TestParseBookSearchParams()
{
var torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"book-search", null}
});
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"book-search", new List<string>()}
});
Assert.IsEmpty(torznabCaps.MovieSearchParams);
torznabCaps = new TorznabCapabilities();
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"book-search", new List<string> {"q", "title"}}
});
Assert.AreEqual(new List<BookSearchParam> { BookSearchParam.Q, BookSearchParam.Title }, torznabCaps.BookSearchParams);
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"book-search", new List<string> {"q", "q"}} // duplicate param
});
Assert.Fail();
}
catch (Exception)
{
// ignored
}
torznabCaps = new TorznabCapabilities();
try {
torznabCaps.ParseCardigannSearchModes(new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}},
{"book-search", new List<string> {"bad"}} // unsupported param
});
Assert.Fail();
}
catch (Exception)
@@ -88,7 +346,6 @@ namespace Jackett.Test.Common.Models
Assert.True(xDocument.Root?.Element("searching")?.HasElements);
Assert.False(xDocument.Root?.Element("categories")?.HasElements);
// TODO: remove params when it's disabled. Review Torznab specs
// test all features disabled
torznabCaps = new TorznabCapabilities
{
@@ -109,7 +366,6 @@ namespace Jackett.Test.Common.Models
Assert.AreEqual("no", xDoumentSearching?.Element("book-search")?.Attribute("available")?.Value);
Assert.AreEqual("q", xDoumentSearching?.Element("book-search")?.Attribute("supportedParams")?.Value);
// TODO: book parameters should be configurable?
// test all features enabled
torznabCaps = new TorznabCapabilities
{
@@ -126,7 +382,10 @@ namespace Jackett.Test.Common.Models
{
MusicSearchParam.Q, MusicSearchParam.Album, MusicSearchParam.Artist, MusicSearchParam.Label, MusicSearchParam.Year
},
BookSearchAvailable = true
BookSearchParams = new List<BookSearchParam>
{
BookSearchParam.Q, BookSearchParam.Title, BookSearchParam.Author
},
};
xDocument = torznabCaps.GetXDocument();
xDoumentSearching = xDocument.Root?.Element("searching");
@@ -141,7 +400,7 @@ namespace Jackett.Test.Common.Models
Assert.AreEqual("yes", xDoumentSearching?.Element("audio-search")?.Attribute("available")?.Value);
Assert.AreEqual("q,album,artist,label,year", xDoumentSearching?.Element("audio-search")?.Attribute("supportedParams")?.Value);
Assert.AreEqual("yes", xDoumentSearching?.Element("book-search")?.Attribute("available")?.Value);
Assert.AreEqual("q,author,title", xDoumentSearching?.Element("book-search")?.Attribute("supportedParams")?.Value);
Assert.AreEqual("q,title,author", xDoumentSearching?.Element("book-search")?.Attribute("supportedParams")?.Value);
// test categories
torznabCaps = new TorznabCapabilities

View File

@@ -55,7 +55,10 @@ namespace Jackett.Test.Torznab
Assert.False(TorznabCaps.MusicSearchArtistAvailable);
Assert.False(TorznabCaps.MusicSearchLabelAvailable);
Assert.False(TorznabCaps.MusicSearchYearAvailable);
Assert.IsEmpty(TorznabCaps.BookSearchParams);
Assert.False(TorznabCaps.BookSearchAvailable);
Assert.False(TorznabCaps.BookSearchTitleAvailable);
Assert.False(TorznabCaps.BookSearchAuthorAvailable);
Assert.AreEqual(0, TorznabCaps.Categories.Count);
// add "int" category (parent category)
@@ -247,7 +250,10 @@ namespace Jackett.Test.Torznab
Links = new List<string>{ "https://example.com" },
Caps = new capabilitiesBlock
{
Modes = new Dictionary<string, List<string>>()
Modes = new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}}
}
},
Search = new searchBlock()
};
@@ -271,7 +277,10 @@ namespace Jackett.Test.Torznab
Assert.False(indexer.TorznabCaps.MusicSearchArtistAvailable);
Assert.False(indexer.TorznabCaps.MusicSearchLabelAvailable);
Assert.False(indexer.TorznabCaps.MusicSearchYearAvailable);
Assert.IsEmpty(indexer.TorznabCaps.BookSearchParams);
Assert.False(indexer.TorznabCaps.BookSearchAvailable);
Assert.False(indexer.TorznabCaps.BookSearchTitleAvailable);
Assert.False(indexer.TorznabCaps.BookSearchAuthorAvailable);
Assert.AreEqual(0, indexer.TorznabCaps.Categories.Count);
definition = new IndexerDefinition // test categories (same as in C# indexer)
@@ -279,7 +288,10 @@ namespace Jackett.Test.Torznab
Links = new List<string>{ "https://example.com" },
Caps = new capabilitiesBlock
{
Modes = new Dictionary<string, List<string>>(),
Modes = new Dictionary<string, List<string>>
{
{"search", new List<string>{"q"}}
},
Categories = new Dictionary<string, string>
{
{"1", TorznabCatType.Movies.Name}, // integer cat (has children)
@@ -334,7 +346,7 @@ namespace Jackett.Test.Torznab
{"tv-search", new List<string>{ "q", "season", "ep", "imdbid", "tvdbid", "rid" }},
{"movie-search", new List<string>{ "q", "imdbid", "tmdbid" }},
{"music-search", new List<string>{ "q", "album", "artist", "label", "year" }},
{"book-search", new List<string>{ "q", "author", "title" }}
{"book-search", new List<string>{ "q", "title", "author" }}
},
Categories = new Dictionary<string, string>()
},
@@ -343,22 +355,52 @@ namespace Jackett.Test.Torznab
indexer = new CardigannIndexer(null, null, null, null, definition);
Assert.True(indexer.TorznabCaps.SearchAvailable);
Assert.AreEqual(
new List<TvSearchParam>
{
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep, TvSearchParam.ImdbId, TvSearchParam.TvdbId, TvSearchParam.RId
},
indexer.TorznabCaps.TvSearchParams
);
Assert.True(indexer.TorznabCaps.TvSearchAvailable);
Assert.True(indexer.TorznabCaps.TvSearchSeasonAvailable);
Assert.True(indexer.TorznabCaps.TvSearchEpAvailable);
// TODO: SupportsImdbTVSearch is disabled in Jackett.Common.Models.TorznabCapabilities.TvSearchImdbAvailable
Assert.False(indexer.TorznabCaps.TvSearchImdbAvailable);
Assert.True(indexer.TorznabCaps.TvSearchTvdbAvailable);
Assert.True(indexer.TorznabCaps.TvSearchTvRageAvailable);
Assert.AreEqual(
new List<MovieSearchParam> { MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId },
new List<MovieSearchParam>
{
MovieSearchParam.Q, MovieSearchParam.ImdbId, MovieSearchParam.TmdbId
},
indexer.TorznabCaps.MovieSearchParams
);
Assert.True(indexer.TorznabCaps.MovieSearchAvailable);
Assert.True(indexer.TorznabCaps.MovieSearchImdbAvailable);
Assert.True(indexer.TorznabCaps.MovieSearchTmdbAvailable);
// TODO: improve this assert
Assert.AreEqual(5, indexer.TorznabCaps.MusicSearchParams.Count);
Assert.AreEqual(
new List<MusicSearchParam>
{
MusicSearchParam.Q, MusicSearchParam.Album, MusicSearchParam.Artist, MusicSearchParam.Label, MusicSearchParam.Year
},
indexer.TorznabCaps.MusicSearchParams
);
Assert.True(indexer.TorznabCaps.MusicSearchAvailable);
Assert.True(indexer.TorznabCaps.MusicSearchAlbumAvailable);
Assert.True(indexer.TorznabCaps.MusicSearchArtistAvailable);
Assert.True(indexer.TorznabCaps.MusicSearchLabelAvailable);
Assert.True(indexer.TorznabCaps.MusicSearchYearAvailable);
Assert.AreEqual(
new List<BookSearchParam>
{
BookSearchParam.Q, BookSearchParam.Title, BookSearchParam.Author
},
indexer.TorznabCaps.BookSearchParams
);
Assert.True(indexer.TorznabCaps.BookSearchAvailable);
Assert.True(indexer.TorznabCaps.BookSearchTitleAvailable);
Assert.True(indexer.TorznabCaps.BookSearchAuthorAvailable);
// test Jackett UI categories (internal JSON) => same code path as C# indexer
// test Torznab caps (XML) => same code path as C# indexer