diff --git a/src/Jackett.Common/Indexers/BaseIndexer.cs b/src/Jackett.Common/Indexers/BaseIndexer.cs index 3b3c035d6..192819c87 100644 --- a/src/Jackett.Common/Indexers/BaseIndexer.cs +++ b/src/Jackett.Common/Indexers/BaseIndexer.cs @@ -283,7 +283,7 @@ namespace Jackett.Common.Indexers var caps = TorznabCaps; if (query.HasSpecifiedCategories) - if (!caps.SupportsCategories(query.Categories)) + if (!caps.Categories.SupportsCategories(query.Categories)) return false; if (caps.TvSearchImdbAvailable && query.IsImdbQuery && query.IsTVSearch) return true; @@ -577,127 +577,29 @@ namespace Jackett.Common.Indexers } } - protected List GetAllTrackerCategories() => categoryMapping.Select(x => x.TrackerCategory).ToList(); + protected List GetAllTrackerCategories() => + TorznabCaps.Categories.GetTrackerCategories(); - protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory, string trackerCategoryDesc = null) - { - categoryMapping.Add(new CategoryMapping(trackerCategory, trackerCategoryDesc, newznabCategory.ID)); - if (!TorznabCaps.Categories.Contains(newznabCategory)) - TorznabCaps.Categories.Add(newznabCategory); + protected void AddCategoryMapping(string trackerCategory, TorznabCategory newznabCategory, string trackerCategoryDesc = null) => + TorznabCaps.Categories.AddCategoryMapping(trackerCategory, newznabCategory, trackerCategoryDesc); - // add 1:1 categories - if (trackerCategoryDesc != null && trackerCategory != null) - { - //TODO convert to int.TryParse() to avoid using throw as flow control - try - { - var trackerCategoryInt = int.Parse(trackerCategory); - var CustomCat = new TorznabCategory(trackerCategoryInt + 100000, trackerCategoryDesc); - if (!TorznabCaps.Categories.Contains(CustomCat)) - TorznabCaps.Categories.Add(CustomCat); - } - catch (FormatException) - { - // trackerCategory is not an integer, continue - } - } - } - - protected void AddCategoryMapping(int trackerCategory, TorznabCategory newznabCategory, string trackerCategoryDesc = null) => AddCategoryMapping(trackerCategory.ToString(), newznabCategory, trackerCategoryDesc); + protected void AddCategoryMapping(int trackerCategory, TorznabCategory newznabCategory, string trackerCategoryDesc = null) => + AddCategoryMapping(trackerCategory.ToString(), newznabCategory, trackerCategoryDesc); protected void AddMultiCategoryMapping(TorznabCategory newznabCategory, params int[] trackerCategories) { foreach (var trackerCat in trackerCategories) - { AddCategoryMapping(trackerCat, newznabCategory); - } } - protected virtual List MapTorznabCapsToTrackers(TorznabQuery query, bool mapChildrenCatsToParent = false) - { - var result = new List(); - foreach (var cat in query.Categories) - { - // use 1:1 mapping to tracker categories for newznab categories >= 100000 - if (cat >= 100000) - { - result.Add((cat - 100000).ToString()); - continue; - } + protected List MapTorznabCapsToTrackers(TorznabQuery query, bool mapChildrenCatsToParent = false) => + TorznabCaps.Categories.MapTorznabCapsToTrackers(query, mapChildrenCatsToParent); - var queryCats = new List { cat }; - var newznabCat = TorznabCatType.AllCats.FirstOrDefault(c => c.ID == cat); - if (newznabCat != null) - { - queryCats.AddRange(newznabCat.SubCategories.Select(c => c.ID)); - } + protected ICollection MapTrackerCatToNewznab(string input) => + TorznabCaps.Categories.MapTrackerCatToNewznab(input); - if (mapChildrenCatsToParent) - { - var parentNewznabCat = TorznabCatType.AllCats.FirstOrDefault(c => c.SubCategories.Contains(newznabCat)); - if (parentNewznabCat != null) - { - queryCats.Add(parentNewznabCat.ID); - } - } - - foreach (var mapping in categoryMapping.Where(c => queryCats.Contains(c.NewzNabCategory))) - { - result.Add(mapping.TrackerCategory); - } - } - - return result.Distinct().ToList(); - } - - protected ICollection MapTrackerCatToNewznab(string input) - { - if (input == null) - return new List(); - - var cats = categoryMapping.Where(m => m.TrackerCategory != null && m.TrackerCategory.ToLowerInvariant() == input.ToLowerInvariant()).Select(c => c.NewzNabCategory).ToList(); - - // 1:1 category mapping - try - { - var trackerCategoryInt = int.Parse(input); - cats.Add(trackerCategoryInt + 100000); - } - catch (FormatException) - { - // input is not an integer, continue - } - - return cats; - } - - protected ICollection MapTrackerCatDescToNewznab(string input) - { - var cats = new List(); - if (null != input) - { - var mapping = categoryMapping.Where(m => m.TrackerCategoryDesc != null && m.TrackerCategoryDesc.ToLowerInvariant() == input.ToLowerInvariant()).FirstOrDefault(); - if (mapping != null) - { - cats.Add(mapping.NewzNabCategory); - - if (mapping.TrackerCategory != null) - { - // 1:1 category mapping - try - { - var trackerCategoryInt = int.Parse(mapping.TrackerCategory); - cats.Add(trackerCategoryInt + 100000); - } - catch (FormatException) - { - // mapping.TrackerCategory is not an integer, continue - } - } - } - } - return cats; - } + protected ICollection MapTrackerCatDescToNewznab(string input) => + TorznabCaps.Categories.MapTrackerCatDescToNewznab(input); private IEnumerable CleanLinks(IEnumerable releases) { @@ -748,7 +650,6 @@ namespace Jackett.Common.Indexers public override TorznabCapabilities TorznabCaps { get; protected set; } - private readonly List categoryMapping = new List(); protected WebClient webclient; protected readonly string downloadUrlBase = ""; } diff --git a/src/Jackett.Common/Models/DTO/Indexer.cs b/src/Jackett.Common/Models/DTO/Indexer.cs index 6a904e371..03b6cd144 100644 --- a/src/Jackett.Common/Models/DTO/Indexer.cs +++ b/src/Jackett.Common/Models/DTO/Indexer.cs @@ -51,11 +51,11 @@ namespace Jackett.Common.Models.DTO site_link = indexer.SiteLink; language = indexer.Language; last_error = indexer.LastError; - potatoenabled = indexer.TorznabCaps.Categories.Any(i => TorznabCatType.Movies.Contains(i)); + potatoenabled = indexer.TorznabCaps.Categories.GetTorznabCategories().Any(i => TorznabCatType.Movies.Contains(i)); alternativesitelinks = indexer.AlternativeSiteLinks; - caps = indexer.TorznabCaps.Categories + caps = indexer.TorznabCaps.Categories.GetTorznabCategories() .GroupBy(p => p.ID) .Select(g => g.First()) .OrderBy(c => c.ID < 100000 ? "z" + c.ID.ToString() : c.Name) diff --git a/src/Jackett.Common/Models/TorznabCapabilities.cs b/src/Jackett.Common/Models/TorznabCapabilities.cs index 0737d1212..223a3578e 100644 --- a/src/Jackett.Common/Models/TorznabCapabilities.cs +++ b/src/Jackett.Common/Models/TorznabCapabilities.cs @@ -71,7 +71,7 @@ namespace Jackett.Common.Models public bool BookSearchTitleAvailable => (BookSearchParams.Contains(BookSearchParam.Title)); public bool BookSearchAuthorAvailable => (BookSearchParams.Contains(BookSearchParam.Author)); - public List Categories { get; set; } + public readonly TorznabCapabilitiesCategories Categories; public TorznabCapabilities() { @@ -80,7 +80,7 @@ namespace Jackett.Common.Models MovieSearchParams = new List(); MusicSearchParams = new List(); BookSearchParams = new List(); - Categories = new List(); + Categories = new TorznabCapabilitiesCategories(); } public void ParseCardigannSearchModes(Dictionary> modes) @@ -219,14 +219,6 @@ namespace Jackett.Common.Models return string.Join(",", parameters); } - public bool SupportsCategories(int[] categories) - { - var subCategories = Categories.SelectMany(c => c.SubCategories); - var allCategories = Categories.Concat(subCategories); - var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.ID)); - return supportsCategory; - } - public XDocument GetXDocument() { var xdoc = new XDocument( @@ -269,7 +261,7 @@ namespace Jackett.Common.Models ) ), new XElement("categories", - from c in Categories.OrderBy(x => x.ID < 100000 ? "z" + x.ID.ToString() : x.Name) + from c in Categories.GetTorznabCategories().OrderBy(x => x.ID < 100000 ? "z" + x.ID.ToString() : x.Name) select new XElement("category", new XAttribute("id", c.ID), new XAttribute("name", c.Name), @@ -295,7 +287,7 @@ namespace Jackett.Common.Models lhs.MovieSearchParams = lhs.MovieSearchParams.Union(rhs.MovieSearchParams).ToList(); lhs.MusicSearchParams = lhs.MusicSearchParams.Union(rhs.MusicSearchParams).ToList(); 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) + lhs.Categories.Concat(rhs.Categories); return lhs; } } diff --git a/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs b/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs new file mode 100644 index 000000000..05f0e8782 --- /dev/null +++ b/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Jackett.Common.Models +{ + public class TorznabCapabilitiesCategories + { + private readonly List _categories = new List(); + private readonly List _categoryMapping = new List(); + + public List GetTorznabCategories() => _categories; + + public List GetTrackerCategories() => _categoryMapping.Select(x => x.TrackerCategory).ToList(); + + public void AddCategoryMapping(string trackerCategory, TorznabCategory torznabCategory, string trackerCategoryDesc = null) + { + _categoryMapping.Add(new CategoryMapping(trackerCategory, trackerCategoryDesc, torznabCategory.ID)); + + if (!_categories.Contains(torznabCategory)) + _categories.Add(torznabCategory); + + // add 1:1 categories + if (trackerCategoryDesc != null && trackerCategory != null) + { + //TODO convert to int.TryParse() to avoid using throw as flow control + try + { + var trackerCategoryInt = int.Parse(trackerCategory); + var customCat = new TorznabCategory(trackerCategoryInt + 100000, trackerCategoryDesc); + if (!_categories.Contains(customCat)) + _categories.Add(customCat); + } + catch (FormatException) + { + // trackerCategory is not an integer, continue + } + } + } + + public List MapTorznabCapsToTrackers(TorznabQuery query, bool mapChildrenCatsToParent = false) + { + var result = new List(); + foreach (var cat in query.Categories) + { + // use 1:1 mapping to tracker categories for newznab categories >= 100000 + if (cat >= 100000) + { + result.Add((cat - 100000).ToString()); + continue; + } + + var queryCats = new List { cat }; + var newznabCat = TorznabCatType.AllCats.FirstOrDefault(c => c.ID == cat); + if (newznabCat != null) + { + queryCats.AddRange(newznabCat.SubCategories.Select(c => c.ID)); + } + + if (mapChildrenCatsToParent) + { + var parentNewznabCat = TorznabCatType.AllCats.FirstOrDefault(c => c.SubCategories.Contains(newznabCat)); + if (parentNewznabCat != null) + { + queryCats.Add(parentNewznabCat.ID); + } + } + + foreach (var mapping in _categoryMapping.Where(c => queryCats.Contains(c.NewzNabCategory))) + { + result.Add(mapping.TrackerCategory); + } + } + + return result.Distinct().ToList(); + } + + public ICollection MapTrackerCatToNewznab(string input) + { + if (input == null) + return new List(); + + var cats = _categoryMapping + .Where(m => m.TrackerCategory != null && m.TrackerCategory.ToLowerInvariant() == input.ToLowerInvariant()) + .Select(c => c.NewzNabCategory).ToList(); + + // 1:1 category mapping + try + { + var trackerCategoryInt = int.Parse(input); + cats.Add(trackerCategoryInt + 100000); + } + catch (FormatException) + { + // input is not an integer, continue + } + + return cats; + } + + public ICollection MapTrackerCatDescToNewznab(string input) + { + var cats = new List(); + if (null != input) + { + var mapping = _categoryMapping + .FirstOrDefault(m => m.TrackerCategoryDesc != null && m.TrackerCategoryDesc.ToLowerInvariant() == input.ToLowerInvariant()); + if (mapping != null) + { + cats.Add(mapping.NewzNabCategory); + + if (mapping.TrackerCategory != null) + { + // 1:1 category mapping + try + { + var trackerCategoryInt = int.Parse(mapping.TrackerCategory); + cats.Add(trackerCategoryInt + 100000); + } + catch (FormatException) + { + // mapping.TrackerCategory is not an integer, continue + } + } + } + } + return cats; + } + + public bool SupportsCategories(int[] categories) + { + if (categories == null) + return false; + var subCategories = _categories.SelectMany(c => c.SubCategories); + var allCategories = _categories.Concat(subCategories); + var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.ID)); + return supportsCategory; + } + + public void Concat(TorznabCapabilitiesCategories rhs) => + // exclude indexer specific categories (>= 100000) + // we don't concat _categoryMapping because it makes no sense for the aggregate indexer + _categories.AddRange(rhs._categories.Where(x => x.ID < 100000).Except(_categories)); + } +} diff --git a/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs b/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs new file mode 100644 index 000000000..609814113 --- /dev/null +++ b/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs @@ -0,0 +1,232 @@ +using System.Linq; +using Jackett.Common.Models; +using NUnit.Framework; +using Assert = NUnit.Framework.Assert; + +namespace Jackett.Test.Common.Models +{ + [TestFixture] + public class TorznabCapabilitiesCategoriesTests + { + [Test] + public void TestConstructor() + { + var tcc = new TorznabCapabilitiesCategories(); + Assert.IsEmpty(tcc.GetTorznabCategories()); + Assert.IsEmpty(tcc.GetTrackerCategories()); + } + + [Test] + public void TestGetTorznabCategories() + { + var tcc = CreateTestDataset(); + var cats = tcc.GetTorznabCategories(); + Assert.AreEqual(7, cats.Count); + Assert.AreEqual(2000, cats[0].ID); + } + + [Test] + public void TestGetTrackerCategories() + { + var tcc = CreateTestDataset(); + var trackerCats = tcc.GetTrackerCategories(); + Assert.AreEqual(6, trackerCats.Count); + Assert.AreEqual("1", trackerCats[0]); + } + + [Test] + public void TestAddCategoryMapping() + { + var tcc = new TorznabCapabilitiesCategories(); + var cats = tcc.GetTorznabCategories(); + + // add "int" category (parent category) + tcc.AddCategoryMapping("1", TorznabCatType.Movies); + Assert.AreEqual(1, cats.Count); + Assert.AreEqual(2000, cats[0].ID); + + // add "string" category (child category) + tcc.AddCategoryMapping("mov_sd", TorznabCatType.MoviesSD); + Assert.AreEqual(2, cats.Count); + Assert.AreEqual(2030, cats[1].ID); + + // add subcategory of books (child category) + tcc.AddCategoryMapping("33", TorznabCatType.BooksComics); + Assert.AreEqual(3, cats.Count); + Assert.AreEqual(7020, cats[2].ID); + + // add int category with description => custom category. it's converted into 2 different categories + tcc.AddCategoryMapping("44", TorznabCatType.ConsoleXbox, "Console/Xbox_c"); + Assert.AreEqual(5, cats.Count); + Assert.AreEqual(1040, cats[3].ID); + Assert.AreEqual(100044, cats[4].ID); + + // TODO: we should add a way to add custom categories for string categories + // https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer#caps-endpoint + // add string category with description. it's converted into 1 category + tcc.AddCategoryMapping("con_wii", TorznabCatType.ConsoleWii, "Console/Wii_c"); + Assert.AreEqual(6, cats.Count); + Assert.AreEqual(1030, cats[5].ID); + + // add another int category with description that maps to ConsoleXbox (there are 2 tracker cats => 1 torznab cat) + tcc.AddCategoryMapping("45", TorznabCatType.ConsoleXbox, "Console/Xbox_c2"); + Assert.AreEqual(7, cats.Count); + Assert.AreEqual(100045, cats[6].ID); // 1040 is duplicated and it is not added + } + + [Test] + public void TestMapTorznabCapsToTrackers() + { + // MapTorznabCapsToTrackers: maps TorznabQuery cats => Tracker cats + var tcc = CreateTestDataset(); + + var query = new TorznabQuery(); // no cats + var trackerCats = tcc.MapTorznabCapsToTrackers(query); + Assert.AreEqual(0, trackerCats.Count); + + query = new TorznabQuery // int category with subcategories (parent cat) + { + Categories = new [] { TorznabCatType.Movies.ID } + }; + trackerCats = tcc.MapTorznabCapsToTrackers(query); + Assert.AreEqual(2, trackerCats.Count); + Assert.AreEqual("1", trackerCats[0]); // Movies + Assert.AreEqual("mov_sd", trackerCats[1]); // Movies SD + + query = new TorznabQuery // string child category + { + Categories = new [] { TorznabCatType.MoviesSD.ID } + }; + trackerCats = tcc.MapTorznabCapsToTrackers(query); + Assert.AreEqual(1, trackerCats.Count); + Assert.AreEqual("mov_sd", trackerCats[0]); // Movies SD + trackerCats = tcc.MapTorznabCapsToTrackers(query, true); // get parent + Assert.AreEqual(2, trackerCats.Count); + Assert.AreEqual("1", trackerCats[0]); // Movies + Assert.AreEqual("mov_sd", trackerCats[1]); // Movies SD + + query = new TorznabQuery // duplicate category (1 toznab cat => 2 indexer cats) + { + Categories = new [] { TorznabCatType.ConsoleXbox.ID } + }; + trackerCats = tcc.MapTorznabCapsToTrackers(query); + Assert.AreEqual(2, trackerCats.Count); + Assert.AreEqual("44", trackerCats[0]); + Assert.AreEqual("45", trackerCats[1]); + + query = new TorznabQuery // custom cat + { + Categories = new [] { 100001 } // Movies + }; + trackerCats = tcc.MapTorznabCapsToTrackers(query); + Assert.AreEqual(1, trackerCats.Count); + Assert.AreEqual("1", trackerCats[0]); // Movies + + query = new TorznabQuery // unknown category + { + Categories = new [] { 9999 } + }; + trackerCats = tcc.MapTorznabCapsToTrackers(query); + Assert.AreEqual(0, trackerCats.Count); + } + + [Test] + public void TestMapTrackerCatToNewznab() + { + // MapTrackerCatToNewznab: maps Tracker cat ID => Torznab cats + var tcc = CreateTestDataset(); + + // TODO: this is wrong, custom cat 100001 doesn't exists (it's not defined by us) + var torznabCats = tcc.MapTrackerCatToNewznab("1").ToList(); + Assert.AreEqual(2, torznabCats.Count); + Assert.AreEqual(2000, torznabCats[0]); + Assert.AreEqual(100001, torznabCats[1]); + + torznabCats = tcc.MapTrackerCatToNewznab("mov_sd").ToList(); + Assert.AreEqual(1, torznabCats.Count); + Assert.AreEqual(2030, torznabCats[0]); + + torznabCats = tcc.MapTrackerCatToNewznab("44").ToList(); // 44 and 45 maps to ConsoleXbox but different custom cat + Assert.AreEqual(2, torznabCats.Count); + Assert.AreEqual(1040, torznabCats[0]); + Assert.AreEqual(100044, torznabCats[1]); + torznabCats = tcc.MapTrackerCatToNewznab("45").ToList(); + Assert.AreEqual(2, torznabCats.Count); + Assert.AreEqual(1040, torznabCats[0]); + Assert.AreEqual(100045, torznabCats[1]); + + // TODO: this is wrong, we are returning cat 109999 which doesn't exist + //torznabCats = tcc.MapTrackerCatToNewznab("9999").ToList(); // unknown cat + //Assert.AreEqual(0, torznabCats.Count); + + torznabCats = tcc.MapTrackerCatToNewznab(null).ToList(); // null + Assert.AreEqual(0, torznabCats.Count); + } + + [Test] + public void TestMapTrackerCatDescToNewznab() + { + // MapTrackerCatDescToNewznab: maps Tracker cat Description => Torznab cats + var tcc = CreateTestDataset(); + + var torznabCats = tcc.MapTrackerCatDescToNewznab("Console/Xbox_c").ToList(); // Console/Xbox_c and Console/Xbox_c2 maps to ConsoleXbox but different custom cat + Assert.AreEqual(2, torznabCats.Count); + Assert.AreEqual(1040, torznabCats[0]); + Assert.AreEqual(100044, torznabCats[1]); + + torznabCats = tcc.MapTrackerCatDescToNewznab("Console/Xbox_c2").ToList(); + Assert.AreEqual(2, torznabCats.Count); + Assert.AreEqual(1040, torznabCats[0]); + Assert.AreEqual(100045, torznabCats[1]); + + torznabCats = tcc.MapTrackerCatDescToNewznab("Console/Wii_c").ToList(); + Assert.AreEqual(1, torznabCats.Count); + Assert.AreEqual(1030, torznabCats[0]); + + torznabCats = tcc.MapTrackerCatDescToNewznab("9999").ToList(); // unknown cat + Assert.AreEqual(0, torznabCats.Count); + + torznabCats = tcc.MapTrackerCatDescToNewznab(null).ToList(); // null + Assert.AreEqual(0, torznabCats.Count); + } + + [Test] + public void TestSupportsCategories() + { + var tcc = CreateTestDataset(); + + Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.Movies.ID })); // parent cat + Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.MoviesSD.ID })); // child cat + Assert.True(tcc.SupportsCategories(new []{ TorznabCatType.Movies.ID, TorznabCatType.MoviesSD.ID })); // parent & child + Assert.True(tcc.SupportsCategories(new []{ 100044 })); // custom cat + // TODO: fix this + //Assert.False(tcc.SupportsCategories(new []{ TorznabCatType.Movies3D.ID })); // not supported child cat + Assert.False(tcc.SupportsCategories(new []{ 9999 })); // unknown cat + Assert.False(tcc.SupportsCategories(new int[]{})); // empty list + Assert.False(tcc.SupportsCategories(null)); // null + } + + [Test] + public void TestConcat() + { + var lhs = new TorznabCapabilitiesCategories(); + var rhs = CreateTestDataset(); + + lhs.Concat(rhs); + Assert.AreEqual(5, lhs.GetTorznabCategories().Count); // removed custom cats + Assert.AreEqual(0, lhs.GetTrackerCategories().Count); // removed tracker mapping + } + + private static TorznabCapabilitiesCategories CreateTestDataset() + { + var tcc = new TorznabCapabilitiesCategories(); + tcc.AddCategoryMapping("1", TorznabCatType.Movies); + tcc.AddCategoryMapping("mov_sd", TorznabCatType.MoviesSD); + tcc.AddCategoryMapping("33", TorznabCatType.BooksComics); + tcc.AddCategoryMapping("44", TorznabCatType.ConsoleXbox, "Console/Xbox_c"); + tcc.AddCategoryMapping("con_wii", TorznabCatType.ConsoleWii, "Console/Wii_c"); + tcc.AddCategoryMapping("45", TorznabCatType.ConsoleXbox, "Console/Xbox_c2"); + return tcc; + } + } +} diff --git a/src/Jackett.Test/Common/Models/TorznabCapabilitiesTests.cs b/src/Jackett.Test/Common/Models/TorznabCapabilitiesTests.cs index 3983109c1..01f75173a 100644 --- a/src/Jackett.Test/Common/Models/TorznabCapabilitiesTests.cs +++ b/src/Jackett.Test/Common/Models/TorznabCapabilitiesTests.cs @@ -41,7 +41,8 @@ namespace Jackett.Test.Common.Models Assert.False(torznabCaps.BookSearchTitleAvailable); Assert.False(torznabCaps.BookSearchAuthorAvailable); - Assert.IsEmpty(torznabCaps.Categories); + Assert.IsEmpty(torznabCaps.Categories.GetTorznabCategories()); + Assert.IsEmpty(torznabCaps.Categories.GetTrackerCategories()); } [Test] @@ -403,10 +404,8 @@ namespace Jackett.Test.Common.Models Assert.AreEqual("q,title,author", xDoumentSearching?.Element("book-search")?.Attribute("supportedParams")?.Value); // test categories - torznabCaps = new TorznabCapabilities - { - Categories = {TorznabCatType.MoviesSD} // child category - }; + torznabCaps = new TorznabCapabilities(); + torznabCaps.Categories.AddCategoryMapping("c1", TorznabCatType.MoviesSD); // child category xDocument = torznabCaps.GetXDocument(); var xDoumentCategories = xDocument.Root?.Element("categories")?.Elements("category").ToList(); Assert.AreEqual(1, xDoumentCategories?.Count); @@ -414,10 +413,9 @@ namespace Jackett.Test.Common.Models Assert.AreEqual(TorznabCatType.MoviesSD.Name, xDoumentCategories?.First().Attribute("name")?.Value); // TODO: child category is duplicated. should we add just parent and child without other subcats? - torznabCaps = new TorznabCapabilities - { - Categories = {TorznabCatType.Movies, TorznabCatType.MoviesSD} // parent and child category - }; + torznabCaps = new TorznabCapabilities(); + torznabCaps.Categories.AddCategoryMapping("c1", TorznabCatType.Movies); // parent and child category + torznabCaps.Categories.AddCategoryMapping("c2", TorznabCatType.MoviesSD); xDocument = torznabCaps.GetXDocument(); xDoumentCategories = xDocument.Root?.Element("categories")?.Elements("category").ToList(); Assert.AreEqual(2, xDoumentCategories?.Count); @@ -430,10 +428,9 @@ namespace Jackett.Test.Common.Models Assert.AreEqual(TorznabCatType.MoviesForeign.ID.ToString(), xDoumentSubCategories?.First().Attribute("id")?.Value); Assert.AreEqual(TorznabCatType.MoviesForeign.Name, xDoumentSubCategories?.First().Attribute("name")?.Value); - // TODO: review Torznab spec about custom cats => https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer#caps-endpoint - torznabCaps = new TorznabCapabilities{ - Categories = {new TorznabCategory(100001, "CustomCat"), TorznabCatType.MoviesSD} // custom category - }; + torznabCaps = new TorznabCapabilities(); + torznabCaps.Categories.AddCategoryMapping("c1", new TorznabCategory(100001, "CustomCat")); // custom category + torznabCaps.Categories.AddCategoryMapping("c2", TorznabCatType.MoviesSD); xDocument = torznabCaps.GetXDocument(); xDoumentCategories = xDocument.Root?.Element("categories")?.Elements("category").ToList(); Assert.AreEqual(2, xDoumentCategories?.Count); @@ -455,7 +452,7 @@ namespace Jackett.Test.Common.Models Assert.IsEmpty(res.MovieSearchParams); Assert.IsEmpty(res.MusicSearchParams); Assert.IsEmpty(res.BookSearchParams); - Assert.IsEmpty(res.Categories); + Assert.IsEmpty(res.Categories.GetTorznabCategories()); torznabCaps1 = new TorznabCapabilities { @@ -463,18 +460,20 @@ namespace Jackett.Test.Common.Models TvSearchParams = new List {TvSearchParam.Q}, MovieSearchParams = new List {MovieSearchParam.Q}, MusicSearchParams = new List {MusicSearchParam.Q}, - BookSearchParams = new List {BookSearchParam.Q}, - Categories = new List{TorznabCatType.Movies, new TorznabCategory(100001, "CustomCat1")} + BookSearchParams = new List {BookSearchParam.Q} }; + torznabCaps1.Categories.AddCategoryMapping("1", TorznabCatType.Movies); + torznabCaps1.Categories.AddCategoryMapping("c1", new TorznabCategory(100001, "CustomCat1")); torznabCaps2 = new TorznabCapabilities { SearchAvailable = false, TvSearchParams = new List {TvSearchParam.Season}, MovieSearchParams = new List {MovieSearchParam.ImdbId}, MusicSearchParams = new List {MusicSearchParam.Artist}, - BookSearchParams = new List {BookSearchParam.Title}, - Categories = new List{TorznabCatType.TVAnime, new TorznabCategory(100002, "CustomCat2")} + BookSearchParams = new List {BookSearchParam.Title} }; + torznabCaps2.Categories.AddCategoryMapping("2", TorznabCatType.TVAnime); + torznabCaps2.Categories.AddCategoryMapping("c2", new TorznabCategory(100002, "CustomCat2")); res = TorznabCapabilities.Concat(torznabCaps1, torznabCaps2); Assert.False(res.SearchAvailable); @@ -482,10 +481,7 @@ namespace Jackett.Test.Common.Models Assert.True(res.MovieSearchParams.Count == 2); Assert.True(res.MusicSearchParams.Count == 2); Assert.True(res.BookSearchParams.Count == 2); - Assert.True(res.Categories.Count == 3); // only CustomCat2 is removed + Assert.True(res.Categories.GetTorznabCategories().Count == 3); // only CustomCat2 is removed } - - // TODO: test SupportsCategories - // TODO: test categories in GetXDocument } } diff --git a/src/Jackett.Test/Torznab/TorznabTests.cs b/src/Jackett.Test/Torznab/TorznabTests.cs index d667d8bc5..5e6132395 100644 --- a/src/Jackett.Test/Torznab/TorznabTests.cs +++ b/src/Jackett.Test/Torznab/TorznabTests.cs @@ -30,7 +30,6 @@ namespace Jackett.Test.Torznab { } - public override TorznabCapabilities TorznabCaps { get; protected set; } public override Task ApplyConfiguration(JToken configJson) => throw new NotImplementedException(); protected override Task> PerformQuery(TorznabQuery query) => throw new NotImplementedException(); @@ -59,162 +58,39 @@ namespace Jackett.Test.Torznab Assert.False(TorznabCaps.BookSearchAvailable); Assert.False(TorznabCaps.BookSearchTitleAvailable); Assert.False(TorznabCaps.BookSearchAuthorAvailable); - Assert.AreEqual(0, TorznabCaps.Categories.Count); + Assert.AreEqual(0, TorznabCaps.Categories.GetTorznabCategories().Count); - // add "int" category (parent category) - AddCategoryMapping(1, TorznabCatType.Movies); - Assert.AreEqual(1, TorznabCaps.Categories.Count); - Assert.AreEqual(2000, TorznabCaps.Categories[0].ID); - - // add "string" category (child category) + // simple category tests + AddCategoryMapping("1", TorznabCatType.Movies); AddCategoryMapping("mov_sd", TorznabCatType.MoviesSD); - Assert.AreEqual(2, TorznabCaps.Categories.Count); - Assert.AreEqual(2030, TorznabCaps.Categories[1].ID); - - // add subcategory of books (child category) - AddCategoryMapping(33, TorznabCatType.BooksComics); - Assert.AreEqual(3, TorznabCaps.Categories.Count); - Assert.AreEqual(7020, TorznabCaps.Categories[2].ID); - - // add int category with description => custom category. it's converted into 2 different categories - AddCategoryMapping(44, TorznabCatType.ConsoleXbox, "Console/Xbox_c"); - Assert.AreEqual(5, TorznabCaps.Categories.Count); - Assert.AreEqual(1040, TorznabCaps.Categories[3].ID); - Assert.AreEqual(100044, TorznabCaps.Categories[4].ID); - - // TODO: we should add a way to add custom categories for string categories - // https://github.com/Sonarr/Sonarr/wiki/Implementing-a-Torznab-indexer#caps-endpoint - // add string category with description. it's converted into 1 category + AddCategoryMapping("33", TorznabCatType.BooksComics); + AddCategoryMapping("44", TorznabCatType.ConsoleXbox, "Console/Xbox_c"); AddCategoryMapping("con_wii", TorznabCatType.ConsoleWii, "Console/Wii_c"); - Assert.AreEqual(6, TorznabCaps.Categories.Count); - Assert.AreEqual(1030, TorznabCaps.Categories[5].ID); + AddCategoryMapping("45", TorznabCatType.ConsoleXbox, "Console/Xbox_c2"); - // add another int category with description that maps to ConsoleXbox (there are 2 tracker cats => 1 torznab cat) - AddCategoryMapping(45, TorznabCatType.ConsoleXbox, "Console/Xbox_c2"); - Assert.AreEqual(7, TorznabCaps.Categories.Count); - Assert.AreEqual(100045, TorznabCaps.Categories[6].ID); // 1040 is duplicated and it is not added - - // TODO: test AddMultiCategoryMapping - // TODO: add duplicates: different trackerCat but same newznabCat - // TODO: duplicates are not working well because we keep 2 internal lists with categories. One is deduplicated - // and the other doesn't - // add duplicate - //AddCategoryMapping(1, TorznabCatType.Movies, "Movies"); - //Assert.AreEqual(6, TorznabCaps.Categories.Count); - - // test MapTorznabCapsToTrackers: maps TorznazQuery cats => Tracker cats - var query = new TorznabQuery(); // no cats + var query = new TorznabQuery // int category with subcategories (parent cat) + { + Categories = new [] { TorznabCatType.Movies.ID } + }; var trackerCats = MapTorznabCapsToTrackers(query); - Assert.AreEqual(0, trackerCats.Count); - - query = new TorznabQuery // a lot of cats (mixed types) - { - Categories = TorznabCaps.Categories.Select(c => c.ID).ToArray() - }; - trackerCats = MapTorznabCapsToTrackers(query); - Assert.AreEqual(6, trackerCats.Count); - Assert.AreEqual("1", trackerCats[0]); - Assert.AreEqual("mov_sd", trackerCats[1]); - Assert.AreEqual("33", trackerCats[2]); - Assert.AreEqual("44", trackerCats[3]); - Assert.AreEqual("45", trackerCats[4]); - Assert.AreEqual("con_wii", trackerCats[5]); - - query = new TorznabQuery // int category with subcategories (parent cat) - { - Categories = new [] { 2000 } // Movies - }; - trackerCats = MapTorznabCapsToTrackers(query); Assert.AreEqual(2, trackerCats.Count); Assert.AreEqual("1", trackerCats[0]); // Movies Assert.AreEqual("mov_sd", trackerCats[1]); // Movies SD - query = new TorznabQuery // string child category - { - Categories = new [] { 2030 } // Movies SD - }; - trackerCats = MapTorznabCapsToTrackers(query); - Assert.AreEqual(1, trackerCats.Count); - Assert.AreEqual("mov_sd", trackerCats[0]); // Movies SD - trackerCats = MapTorznabCapsToTrackers(query, true); // get parent - Assert.AreEqual(2, trackerCats.Count); - Assert.AreEqual("1", trackerCats[0]); // Movies - Assert.AreEqual("mov_sd", trackerCats[1]); // Movies SD - - query = new TorznabQuery // duplicate category (1 toznab cat => 2 indexer cats) - { - Categories = new [] { 1040 } // ConsoleXbox - }; - trackerCats = MapTorznabCapsToTrackers(query); - Assert.AreEqual(2, trackerCats.Count); - Assert.AreEqual("44", trackerCats[0]); - Assert.AreEqual("45", trackerCats[1]); - - query = new TorznabQuery // custom cat - { - Categories = new [] { 100001 } // Movies - }; - trackerCats = MapTorznabCapsToTrackers(query); - Assert.AreEqual(1, trackerCats.Count); - Assert.AreEqual("1", trackerCats[0]); // Movies - - query = new TorznabQuery // unknown category - { - Categories = new [] { 9999 } - }; - trackerCats = MapTorznabCapsToTrackers(query); - Assert.AreEqual(0, trackerCats.Count); - // TODO: this is wrong, custom cat 100001 doesn't exists (it's not defined by us) - // test MapTrackerCatToNewznab: maps Tracker cat ID => Torznab cats var torznabCats = MapTrackerCatToNewznab("1").ToList(); Assert.AreEqual(2, torznabCats.Count); Assert.AreEqual(2000, torznabCats[0]); Assert.AreEqual(100001, torznabCats[1]); - torznabCats = MapTrackerCatToNewznab("mov_sd").ToList(); - Assert.AreEqual(1, torznabCats.Count); - Assert.AreEqual(2030, torznabCats[0]); - - torznabCats = MapTrackerCatToNewznab("44").ToList(); // 44 and 45 maps to ConsoleXbox but different custom cat - Assert.AreEqual(2, torznabCats.Count); - Assert.AreEqual(1040, torznabCats[0]); - Assert.AreEqual(100044, torznabCats[1]); - torznabCats = MapTrackerCatToNewznab("45").ToList(); - Assert.AreEqual(2, torznabCats.Count); - Assert.AreEqual(1040, torznabCats[0]); - Assert.AreEqual(100045, torznabCats[1]); - - // TODO: this is wrong, we are returning cat 109999 which doesn't exist - //torznabCats = MapTrackerCatToNewznab("9999").ToList(); // unknown cat - //Assert.AreEqual(0, torznabCats.Count); - - torznabCats = MapTrackerCatToNewznab(null).ToList(); // null - Assert.AreEqual(0, torznabCats.Count); - - // TODO: I think this method should be removed because description can be non-unique - // test MapTrackerCatDescToNewznab: maps Tracker cat Description => Torznab cats - torznabCats = MapTrackerCatDescToNewznab("Console/Xbox_c").ToList(); // Console/Xbox_c and Console/Xbox_c2 maps to ConsoleXbox but different custom cat - Assert.AreEqual(2, torznabCats.Count); - Assert.AreEqual(1040, torznabCats[0]); - Assert.AreEqual(100044, torznabCats[1]); - - torznabCats = MapTrackerCatDescToNewznab("Console/Xbox_c2").ToList(); - Assert.AreEqual(2, torznabCats.Count); - Assert.AreEqual(1040, torznabCats[0]); - Assert.AreEqual(100045, torznabCats[1]); - torznabCats = MapTrackerCatDescToNewznab("Console/Wii_c").ToList(); Assert.AreEqual(1, torznabCats.Count); Assert.AreEqual(1030, torznabCats[0]); - torznabCats = MapTrackerCatDescToNewznab("9999").ToList(); // unknown cat - Assert.AreEqual(0, torznabCats.Count); - - torznabCats = MapTrackerCatDescToNewznab(null).ToList(); // null - Assert.AreEqual(0, torznabCats.Count); - - // TODO: move these methods to TorznabCaps or TorznabQuery classess + // TODO: test AddMultiCategoryMapping + // TODO: add duplicates: different trackerCat but same newznabCat + // TODO: duplicates are not working well because we keep 2 internal lists with categories. One is deduplicated + // and the other doesn't // test Jackett UI categories (internal JSON) var dto = new Jackett.Common.Models.DTO.Indexer(this); @@ -281,7 +157,7 @@ namespace Jackett.Test.Torznab Assert.False(indexer.TorznabCaps.BookSearchAvailable); Assert.False(indexer.TorznabCaps.BookSearchTitleAvailable); Assert.False(indexer.TorznabCaps.BookSearchAuthorAvailable); - Assert.AreEqual(0, indexer.TorznabCaps.Categories.Count); + Assert.AreEqual(0, indexer.TorznabCaps.Categories.GetTorznabCategories().Count); definition = new IndexerDefinition // test categories (same as in C# indexer) { @@ -325,16 +201,16 @@ namespace Jackett.Test.Torznab indexer = new CardigannIndexer(null, null, null, null, definition); // TODO: test duplicates - Assert.AreEqual(7, indexer.TorznabCaps.Categories.Count); - Assert.AreEqual(2000, indexer.TorznabCaps.Categories[0].ID); - Assert.AreEqual(2030, indexer.TorznabCaps.Categories[1].ID); - Assert.AreEqual(7020, indexer.TorznabCaps.Categories[2].ID); - Assert.AreEqual(1040, indexer.TorznabCaps.Categories[3].ID); - Assert.AreEqual(100044, indexer.TorznabCaps.Categories[4].ID); - Assert.AreEqual(1030, indexer.TorznabCaps.Categories[5].ID); - Assert.AreEqual(100045, indexer.TorznabCaps.Categories[6].ID); + var cats = indexer.TorznabCaps.Categories.GetTorznabCategories(); + Assert.AreEqual(7, cats.Count); + Assert.AreEqual(2000, cats[0].ID); + Assert.AreEqual(2030, cats[1].ID); + Assert.AreEqual(7020, cats[2].ID); + Assert.AreEqual(1040, cats[3].ID); + Assert.AreEqual(100044, cats[4].ID); + Assert.AreEqual(1030, cats[5].ID); + Assert.AreEqual(100045, cats[6].ID); - // TODO: we are not validating modes or params in each mode. ie: search is not required/supported and it's used definition = new IndexerDefinition // test search modes { Links = new List{ "https://example.com" },