diff --git a/src/Jackett.Common/Indexers/BaseIndexer.cs b/src/Jackett.Common/Indexers/BaseIndexer.cs index 978da4b4f..c83ab8451 100644 --- a/src/Jackett.Common/Indexers/BaseIndexer.cs +++ b/src/Jackett.Common/Indexers/BaseIndexer.cs @@ -266,9 +266,21 @@ namespace Jackett.Common.Indexers if (query.Categories.Length == 0) return results; - var filteredResults = results.Where( - result => result.Category?.Any() != true || query.Categories.Intersect(result.Category).Any() || - TorznabCatType.QueryContainsParentCategory(query.Categories, result.Category)); + // TODO: move this code to TorznabCapabilitiesCategories and use indexer tree instead of general + // expand parent categories from the query + var expandedQueryCats = new List(); + foreach (var queryCategory in query.Categories) + { + expandedQueryCats.Add(queryCategory); + var parentCat = TorznabCatType.ParentCats.FirstOrDefault(c => c.ID == queryCategory); + if (parentCat != null) + expandedQueryCats.AddRange(parentCat.SubCategories.Select(c => c.ID)); + } + + var filteredResults = results.Where(result => + result.Category?.Any() != true || + expandedQueryCats.Intersect(result.Category).Any() + ); return filteredResults; } diff --git a/src/Jackett.Common/Models/DTO/Indexer.cs b/src/Jackett.Common/Models/DTO/Indexer.cs index 03b6cd144..bee2ef0ab 100644 --- a/src/Jackett.Common/Models/DTO/Indexer.cs +++ b/src/Jackett.Common/Models/DTO/Indexer.cs @@ -51,14 +51,11 @@ namespace Jackett.Common.Models.DTO site_link = indexer.SiteLink; language = indexer.Language; last_error = indexer.LastError; - potatoenabled = indexer.TorznabCaps.Categories.GetTorznabCategories().Any(i => TorznabCatType.Movies.Contains(i)); + potatoenabled = indexer.TorznabCaps.Categories.GetTorznabCategoryTree().Any(i => TorznabCatType.Movies.Contains(i)); alternativesitelinks = indexer.AlternativeSiteLinks; - caps = indexer.TorznabCaps.Categories.GetTorznabCategories() - .GroupBy(p => p.ID) - .Select(g => g.First()) - .OrderBy(c => c.ID < 100000 ? "z" + c.ID.ToString() : c.Name) + caps = indexer.TorznabCaps.Categories.GetTorznabCategoryList(true) .Select(c => new Capability { ID = c.ID.ToString(), diff --git a/src/Jackett.Common/Models/TorznabCapabilities.cs b/src/Jackett.Common/Models/TorznabCapabilities.cs index 223a3578e..b7048c5a1 100644 --- a/src/Jackett.Common/Models/TorznabCapabilities.cs +++ b/src/Jackett.Common/Models/TorznabCapabilities.cs @@ -250,7 +250,7 @@ namespace Jackett.Common.Models new XAttribute("available", MusicSearchAvailable ? "yes" : "no"), new XAttribute("supportedParams", SupportedMusicSearchParams()) ), - // inconsistend but apparently already used by various newznab indexers (see #1896) + // inconsistent but apparently already used by various newznab indexers (see #1896) new XElement("audio-search", new XAttribute("available", MusicSearchAvailable ? "yes" : "no"), new XAttribute("supportedParams", SupportedMusicSearchParams()) @@ -261,7 +261,7 @@ namespace Jackett.Common.Models ) ), new XElement("categories", - from c in Categories.GetTorznabCategories().OrderBy(x => x.ID < 100000 ? "z" + x.ID.ToString() : x.Name) + from c in Categories.GetTorznabCategoryTree(true) select new XElement("category", new XAttribute("id", c.ID), new XAttribute("name", c.Name), diff --git a/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs b/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs index 05f0e8782..d2bd97e75 100644 --- a/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs +++ b/src/Jackett.Common/Models/TorznabCapabilitiesCategories.cs @@ -6,21 +6,52 @@ namespace Jackett.Common.Models { public class TorznabCapabilitiesCategories { - private readonly List _categories = new List(); private readonly List _categoryMapping = new List(); - - public List GetTorznabCategories() => _categories; + private readonly List _torznabCategoryTree = new List(); public List GetTrackerCategories() => _categoryMapping.Select(x => x.TrackerCategory).ToList(); + public List GetTorznabCategoryTree(bool sorted = false) + { + if (!sorted) + return _torznabCategoryTree; + + // we build a new tree, original is unsorted + // first torznab categories ordered by id and then custom cats ordered by name + var sortedTree = _torznabCategoryTree + .Select(c => + { + var sortedSubCats = c.SubCategories.OrderBy(x => x.ID); + var newCat = new TorznabCategory(c.ID, c.Name); + newCat.SubCategories.AddRange(sortedSubCats); + return newCat; + }).OrderBy(x => x.ID > 100000 ? "zzz" + x.Name : x.ID.ToString()).ToList(); + + return sortedTree; + } + + public List GetTorznabCategoryList(bool sorted = false) + { + var tree = GetTorznabCategoryTree(sorted); + + // create a flat list (without subcategories) + var newFlatList = new List(); + foreach (var cat in tree) + { + newFlatList.Add(cat.CopyWithoutSubCategories()); + newFlatList.AddRange(cat.SubCategories); + } + return newFlatList; + } + public void AddCategoryMapping(string trackerCategory, TorznabCategory torznabCategory, string trackerCategoryDesc = null) { + // add torznab cat _categoryMapping.Add(new CategoryMapping(trackerCategory, trackerCategoryDesc, torznabCategory.ID)); + AddTorznabCategoryTree(torznabCategory); - if (!_categories.Contains(torznabCategory)) - _categories.Add(torznabCategory); - - // add 1:1 categories + // TODO: fix this. it's only working for integer "trackerCategory" + // create custom cats (1:1 categories) if (trackerCategoryDesc != null && trackerCategory != null) { //TODO convert to int.TryParse() to avoid using throw as flow control @@ -28,8 +59,7 @@ namespace Jackett.Common.Models { var trackerCategoryInt = int.Parse(trackerCategory); var customCat = new TorznabCategory(trackerCategoryInt + 100000, trackerCategoryDesc); - if (!_categories.Contains(customCat)) - _categories.Add(customCat); + AddTorznabCategoryTree(customCat); } catch (FormatException) { @@ -131,15 +161,54 @@ namespace Jackett.Common.Models { if (categories == null) return false; - var subCategories = _categories.SelectMany(c => c.SubCategories); - var allCategories = _categories.Concat(subCategories); + var subCategories = _torznabCategoryTree.SelectMany(c => c.SubCategories); + var allCategories = _torznabCategoryTree.Concat(subCategories); var supportsCategory = allCategories.Any(i => categories.Any(c => c == i.ID)); return supportsCategory; } - public void Concat(TorznabCapabilitiesCategories rhs) => + 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)); + rhs.GetTorznabCategoryList().Where(x => x.ID < 100000).ToList().ForEach(AddTorznabCategoryTree); + } + + private void AddTorznabCategoryTree(TorznabCategory torznabCategory) + { + // build the category tree + if (TorznabCatType.ParentCats.Contains(torznabCategory)) + { + // parent cat + if (!_torznabCategoryTree.Contains(torznabCategory)) + _torznabCategoryTree.Add(torznabCategory.CopyWithoutSubCategories()); + } + else + { + // child or custom cat + var parentCat = TorznabCatType.ParentCats.FirstOrDefault(c => c.Contains(torznabCategory)); + if (parentCat != null) + { + // child cat + var nodeCat = _torznabCategoryTree.FirstOrDefault(c => c.Equals(parentCat)); + if (nodeCat != null) + { + // parent cat already exists + if (!nodeCat.Contains(torznabCategory)) + nodeCat.SubCategories.Add(torznabCategory); + } + else + { + // create parent cat and add child + nodeCat = parentCat.CopyWithoutSubCategories(); + nodeCat.SubCategories.Add(torznabCategory); + _torznabCategoryTree.Add(nodeCat); + } + } + else + // custom cat + _torznabCategoryTree.Add(torznabCategory); + } + } } } diff --git a/src/Jackett.Common/Models/TorznabCatType.cs b/src/Jackett.Common/Models/TorznabCatType.cs index 30b31a978..72529345c 100644 --- a/src/Jackett.Common/Models/TorznabCatType.cs +++ b/src/Jackett.Common/Models/TorznabCatType.cs @@ -265,27 +265,8 @@ namespace Jackett.Common.Models Other.SubCategories.AddRange(new List {OtherMisc, OtherHashed}); } - public static bool QueryContainsParentCategory(int[] queryCats, ICollection releaseCats) - { - //return (from releaseCat in releaseCats - // select AllCats.FirstOrDefault(c => c.ID == releaseCat) - // into cat - // where cat != null && queryCats != null - // select cat.SubCategories.Any(c => queryCats.Contains(c.ID))) - // .FirstOrDefault(); - // Is equal to: - foreach (var releaseCat in releaseCats) - { - var cat = AllCats.FirstOrDefault(c => c.ID == releaseCat); - if (cat != null && queryCats != null) - return cat.SubCategories.Any(c => queryCats.Contains(c.ID)); - } - - return false; - } - - public static string GetCatDesc(int newznabcat) => - AllCats.FirstOrDefault(c => c.ID == newznabcat)?.Name ?? string.Empty; + public static string GetCatDesc(int torznabCatId) => + AllCats.FirstOrDefault(c => c.ID == torznabCatId)?.Name ?? string.Empty; public static TorznabCategory GetCatByName(string name) => AllCats.FirstOrDefault(c => c.Name == name); } diff --git a/src/Jackett.Common/Models/TorznabCategory.cs b/src/Jackett.Common/Models/TorznabCategory.cs index d57aa101b..83c722bcd 100644 --- a/src/Jackett.Common/Models/TorznabCategory.cs +++ b/src/Jackett.Common/Models/TorznabCategory.cs @@ -34,5 +34,7 @@ namespace Jackett.Common.Models // Get Hash code should be calculated off read only properties. // ID is not readonly public override int GetHashCode() => ID; + + public TorznabCategory CopyWithoutSubCategories() => new TorznabCategory(ID, Name); } } diff --git a/src/Jackett.Test/Common/Indexers/BaseWebIndexerTests.cs b/src/Jackett.Test/Common/Indexers/BaseWebIndexerTests.cs index f2b2ba9ac..7a50685bf 100644 --- a/src/Jackett.Test/Common/Indexers/BaseWebIndexerTests.cs +++ b/src/Jackett.Test/Common/Indexers/BaseWebIndexerTests.cs @@ -38,7 +38,7 @@ namespace Jackett.Test.Common.Indexers Assert.False(caps.BookSearchAvailable); Assert.False(caps.BookSearchTitleAvailable); Assert.False(caps.BookSearchAuthorAvailable); - Assert.AreEqual(0, caps.Categories.GetTorznabCategories().Count); + Assert.AreEqual(0, caps.Categories.GetTorznabCategoryTree().Count); } [Test] @@ -66,27 +66,25 @@ namespace Jackett.Test.Common.Indexers var filteredResults = indexer._FilterResults(query, results).ToList(); Assert.AreEqual(4, filteredResults.Count); - // TODO: fix this, we should return MoviesSD and null query = new TorznabQuery // with child category { Categories = new [] { TorznabCatType.MoviesSD.ID } }; filteredResults = indexer._FilterResults(query, results).ToList(); + Assert.AreEqual(2, filteredResults.Count); + Assert.AreEqual(TorznabCatType.MoviesSD.ID, filteredResults[0].Category.First()); + Assert.AreEqual(null, filteredResults[1].Category); + + query = new TorznabQuery // with parent category + { + Categories = new [] { TorznabCatType.Movies.ID } + }; + filteredResults = indexer._FilterResults(query, results).ToList(); Assert.AreEqual(3, filteredResults.Count); Assert.AreEqual(TorznabCatType.Movies.ID, filteredResults[0].Category.First()); Assert.AreEqual(TorznabCatType.MoviesSD.ID, filteredResults[1].Category.First()); Assert.AreEqual(null, filteredResults[2].Category); - // TODO: fix this, we should return Movies, MoviesSD and null - query = new TorznabQuery // with parent category - { - Categories = new [] { TorznabCatType.Movies.ID } - }; - filteredResults = indexer._FilterResults(query, results).ToList(); - Assert.AreEqual(2, filteredResults.Count); - Assert.AreEqual(TorznabCatType.Movies.ID, filteredResults[0].Category.First()); - Assert.AreEqual(null, filteredResults[1].Category); - query = new TorznabQuery // with custom category { Categories = new [] { 100004 } @@ -104,9 +102,17 @@ namespace Jackett.Test.Common.Indexers // you can find more complex tests in TorznabCapabilitiesCategoriesTests.cs indexer._AddCategoryMapping("11", TorznabCatType.MoviesSD, "MoviesSD"); - Assert.AreEqual(2, indexer.TorznabCaps.Categories.GetTorznabCategories().Count); + var expected = new List + { + TorznabCatType.Movies.CopyWithoutSubCategories(), + new TorznabCategory(100011, "MoviesSD") + }; + expected[0].SubCategories.Add(TorznabCatType.MoviesSD.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, indexer.TorznabCaps.Categories.GetTorznabCategoryTree()); + indexer._AddCategoryMapping(14, TorznabCatType.MoviesHD); - Assert.AreEqual(3, indexer.TorznabCaps.Categories.GetTorznabCategories().Count); + expected[0].SubCategories.Add(TorznabCatType.MoviesHD.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, indexer.TorznabCaps.Categories.GetTorznabCategoryTree()); } [Test] @@ -115,7 +121,7 @@ namespace Jackett.Test.Common.Indexers var indexer = new TestWebIndexer(); indexer._AddMultiCategoryMapping(TorznabCatType.MoviesHD,19, 18); - Assert.AreEqual(1, indexer.TorznabCaps.Categories.GetTorznabCategories().Count); + Assert.AreEqual(1, indexer.TorznabCaps.Categories.GetTorznabCategoryTree().Count); } [Test] diff --git a/src/Jackett.Test/Common/Indexers/CardigannIndexerTests.cs b/src/Jackett.Test/Common/Indexers/CardigannIndexerTests.cs index ac2dd9681..b45a890a5 100644 --- a/src/Jackett.Test/Common/Indexers/CardigannIndexerTests.cs +++ b/src/Jackett.Test/Common/Indexers/CardigannIndexerTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Jackett.Common.Indexers; using Jackett.Common.Models; +using Jackett.Test.TestHelpers; using NUnit.Framework; namespace Jackett.Test.Common.Indexers @@ -48,7 +49,7 @@ namespace Jackett.Test.Common.Indexers Assert.False(indexer.TorznabCaps.BookSearchAvailable); Assert.False(indexer.TorznabCaps.BookSearchTitleAvailable); Assert.False(indexer.TorznabCaps.BookSearchAuthorAvailable); - Assert.AreEqual(0, indexer.TorznabCaps.Categories.GetTorznabCategories().Count); + Assert.AreEqual(0, indexer.TorznabCaps.Categories.GetTorznabCategoryTree().Count); definition = new IndexerDefinition // test categories (same as in C# indexer) { @@ -91,16 +92,20 @@ namespace Jackett.Test.Common.Indexers }; indexer = new CardigannIndexer(null, null, null, null, definition); - // TODO: test duplicates - 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(7030, 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); + // test categories + var expected = new List + { + TorznabCatType.Movies.CopyWithoutSubCategories(), + TorznabCatType.Books.CopyWithoutSubCategories(), + TorznabCatType.Console.CopyWithoutSubCategories(), + new TorznabCategory(100044, "Console/Xbox_c"), + new TorznabCategory(100045, "Console/Xbox_c2") + }; + expected[0].SubCategories.Add(TorznabCatType.MoviesSD.CopyWithoutSubCategories()); + expected[1].SubCategories.Add(TorznabCatType.BooksComics.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleXBox.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleWii.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, indexer.TorznabCaps.Categories.GetTorznabCategoryTree()); definition = new IndexerDefinition // test search modes { diff --git a/src/Jackett.Test/Common/Models/DTO/IndexerTests.cs b/src/Jackett.Test/Common/Models/DTO/IndexerTests.cs index 7ef66a1b3..52b08fbdd 100644 --- a/src/Jackett.Test/Common/Models/DTO/IndexerTests.cs +++ b/src/Jackett.Test/Common/Models/DTO/IndexerTests.cs @@ -37,14 +37,16 @@ namespace Jackett.Test.Common.Models.DTO // test Jackett UI categories (internal JSON) var dto = new Indexer(indexer); var dtoCaps = dto.caps.ToList(); - Assert.AreEqual(7, dtoCaps.Count); - Assert.AreEqual("100044", dtoCaps[0].ID); - Assert.AreEqual("100045", dtoCaps[1].ID); - Assert.AreEqual("1030", dtoCaps[2].ID); - Assert.AreEqual("1040", dtoCaps[3].ID); - Assert.AreEqual("2000", dtoCaps[4].ID); - Assert.AreEqual("2030", dtoCaps[5].ID); + Assert.AreEqual(9, dtoCaps.Count); + Assert.AreEqual("1000", dtoCaps[0].ID); + Assert.AreEqual("1030", dtoCaps[1].ID); + Assert.AreEqual("1040", dtoCaps[2].ID); + Assert.AreEqual("2000", dtoCaps[3].ID); + Assert.AreEqual("2030", dtoCaps[4].ID); + Assert.AreEqual("7000", dtoCaps[5].ID); Assert.AreEqual("7030", dtoCaps[6].ID); + Assert.AreEqual("100044", dtoCaps[7].ID); + Assert.AreEqual("100040", dtoCaps[8].ID); // movies categories enable potato search Assert.True(dto.potatoenabled); diff --git a/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs b/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs index 48a5761a1..9707e88fd 100644 --- a/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs +++ b/src/Jackett.Test/Common/Models/TorznabCapabilitiesCategoriesTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using Jackett.Common.Models; using Jackett.Test.TestHelpers; @@ -6,10 +7,6 @@ using Assert = NUnit.Framework.Assert; namespace Jackett.Test.Common.Models { - // TODO: add duplicates: different trackerCat but same newznabCat - // TODO: duplicates are not working well because we keep 2 internal lists with categories. One is de-duplicated - // and the other doesn't - [TestFixture] public class TorznabCapabilitiesCategoriesTests { @@ -17,19 +14,10 @@ namespace Jackett.Test.Common.Models public void TestConstructor() { var tcc = new TorznabCapabilitiesCategories(); - Assert.IsEmpty(tcc.GetTorznabCategories()); + Assert.IsEmpty(tcc.GetTorznabCategoryTree()); 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() { @@ -39,44 +27,156 @@ namespace Jackett.Test.Common.Models Assert.AreEqual("1", trackerCats[0]); } + [Test] + public void TestGetTorznabCategoryTree() + { + var tcc = CreateTestDataset(); + + // unsorted tree + var cats = tcc.GetTorznabCategoryTree(); + var expected = new List + { + TorznabCatType.Movies.CopyWithoutSubCategories(), + TorznabCatType.Books.CopyWithoutSubCategories(), + TorznabCatType.Console.CopyWithoutSubCategories(), + new TorznabCategory(100044, "Console/Xbox_c"), + new TorznabCategory(100040, "Console/Xbox_c2") + }; + expected[0].SubCategories.Add(TorznabCatType.MoviesSD.CopyWithoutSubCategories()); + expected[1].SubCategories.Add(TorznabCatType.BooksComics.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleXBox.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleWii.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, cats); + + // sorted tree + cats = tcc.GetTorznabCategoryTree(true); + expected = new List + { + TorznabCatType.Console.CopyWithoutSubCategories(), + TorznabCatType.Movies.CopyWithoutSubCategories(), + TorznabCatType.Books.CopyWithoutSubCategories(), + new TorznabCategory(100044, "Console/Xbox_c"), + new TorznabCategory(100040, "Console/Xbox_c2") + }; + expected[0].SubCategories.Add(TorznabCatType.ConsoleWii.CopyWithoutSubCategories()); + expected[0].SubCategories.Add(TorznabCatType.ConsoleXBox.CopyWithoutSubCategories()); + expected[1].SubCategories.Add(TorznabCatType.MoviesSD.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.BooksComics.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, cats); + } + + [Test] + public void TestGetTorznabCategoryList() + { + var tcc = CreateTestDataset(); + + // unsorted list + var cats = tcc.GetTorznabCategoryList(); + var expected = new List + { + TorznabCatType.Movies.CopyWithoutSubCategories(), + TorznabCatType.MoviesSD.CopyWithoutSubCategories(), + TorznabCatType.Books.CopyWithoutSubCategories(), + TorznabCatType.BooksComics.CopyWithoutSubCategories(), + TorznabCatType.Console.CopyWithoutSubCategories(), + TorznabCatType.ConsoleXBox.CopyWithoutSubCategories(), + TorznabCatType.ConsoleWii.CopyWithoutSubCategories(), + new TorznabCategory(100044, "Console/Xbox_c"), + new TorznabCategory(100040, "Console/Xbox_c2") + }; + TestCategories.CompareCategoryTrees(expected, cats); + + // sorted list + cats = tcc.GetTorznabCategoryList(true); + expected = new List + { + TorznabCatType.Console.CopyWithoutSubCategories(), + TorznabCatType.ConsoleWii.CopyWithoutSubCategories(), + TorznabCatType.ConsoleXBox.CopyWithoutSubCategories(), + TorznabCatType.Movies.CopyWithoutSubCategories(), + TorznabCatType.MoviesSD.CopyWithoutSubCategories(), + TorznabCatType.Books.CopyWithoutSubCategories(), + TorznabCatType.BooksComics.CopyWithoutSubCategories(), + new TorznabCategory(100044, "Console/Xbox_c"), + new TorznabCategory(100040, "Console/Xbox_c2") + }; + TestCategories.CompareCategoryTrees(expected, cats); + } + [Test] public void TestAddCategoryMapping() { var tcc = new TorznabCapabilitiesCategories(); - var cats = tcc.GetTorznabCategories(); + var cats = tcc.GetTorznabCategoryTree(); // add "int" category (parent category) + // + Movies tcc.AddCategoryMapping("1", TorznabCatType.Movies); - Assert.AreEqual(1, cats.Count); - Assert.AreEqual(2000, cats[0].ID); + var expected = new List + { + TorznabCatType.Movies.CopyWithoutSubCategories() + }; + TestCategories.CompareCategoryTrees(expected, cats); // add "string" category (child category) + // - Movies + // + MoviesSD tcc.AddCategoryMapping("mov_sd", TorznabCatType.MoviesSD); - Assert.AreEqual(2, cats.Count); - Assert.AreEqual(2030, cats[1].ID); + expected[0].SubCategories.Add(TorznabCatType.MoviesSD.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, cats); // add subcategory of books (child category) + // - Movies + // - MoviesSD + // + Books + // + BooksComics tcc.AddCategoryMapping("33", TorznabCatType.BooksComics); - Assert.AreEqual(3, cats.Count); - Assert.AreEqual(7030, cats[2].ID); + expected.Add(TorznabCatType.Books.CopyWithoutSubCategories()); + expected[1].SubCategories.Add(TorznabCatType.BooksComics.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, cats); // add int category with description => custom category. it's converted into 2 different categories + // - Movies + // - MoviesSD + // - Books + // - BooksComics + // + Console + // + ConsoleXBox + // + Custom Cat "Console/Xbox_c" 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); + expected.Add(TorznabCatType.Console.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleXBox.CopyWithoutSubCategories()); + expected.Add(new TorznabCategory(100044, "Console/Xbox_c")); + TestCategories.CompareCategoryTrees(expected, cats); // 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 + // - Movies + // - MoviesSD + // - Books + // - BooksComics + // - Console + // - ConsoleXBox + // + ConsoleWii + // - Custom Cat "Console/Xbox_c" tcc.AddCategoryMapping("con_wii", TorznabCatType.ConsoleWii, "Console/Wii_c"); - Assert.AreEqual(6, cats.Count); - Assert.AreEqual(1030, cats[5].ID); + expected[2].SubCategories.Add(TorznabCatType.ConsoleWii.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, cats); // add another int category with description that maps to ConsoleXbox (there are 2 tracker cats => 1 torznab cat) + // - Movies + // - MoviesSD + // - Books + // - BooksComics + // - Console + // - ConsoleXBox (this is not added again) + // - ConsoleWii + // - Custom Cat "Console/Xbox_c" + // + Custom Cat "Console/Xbox_c2" 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 + expected.Add(new TorznabCategory(100045, "Console/Xbox_c2")); + TestCategories.CompareCategoryTrees(expected, cats); } [Test] @@ -117,7 +217,7 @@ namespace Jackett.Test.Common.Models trackerCats = tcc.MapTorznabCapsToTrackers(query); Assert.AreEqual(2, trackerCats.Count); Assert.AreEqual("44", trackerCats[0]); - Assert.AreEqual("45", trackerCats[1]); + Assert.AreEqual("40", trackerCats[1]); query = new TorznabQuery // custom cat { @@ -155,10 +255,10 @@ namespace Jackett.Test.Common.Models Assert.AreEqual(2, torznabCats.Count); Assert.AreEqual(1040, torznabCats[0]); Assert.AreEqual(100044, torznabCats[1]); - torznabCats = tcc.MapTrackerCatToNewznab("45").ToList(); + torznabCats = tcc.MapTrackerCatToNewznab("40").ToList(); Assert.AreEqual(2, torznabCats.Count); Assert.AreEqual(1040, torznabCats[0]); - Assert.AreEqual(100045, torznabCats[1]); + Assert.AreEqual(100040, torznabCats[1]); // TODO: this is wrong, we are returning cat 109999 which doesn't exist //torznabCats = tcc.MapTrackerCatToNewznab("9999").ToList(); // unknown cat @@ -182,7 +282,7 @@ namespace Jackett.Test.Common.Models torznabCats = tcc.MapTrackerCatDescToNewznab("Console/Xbox_c2").ToList(); Assert.AreEqual(2, torznabCats.Count); Assert.AreEqual(1040, torznabCats[0]); - Assert.AreEqual(100045, torznabCats[1]); + Assert.AreEqual(100040, torznabCats[1]); torznabCats = tcc.MapTrackerCatDescToNewznab("Console/Wii_c").ToList(); Assert.AreEqual(1, torznabCats.Count); @@ -203,9 +303,8 @@ namespace Jackett.Test.Common.Models 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.True(tcc.SupportsCategories(new []{ 100040 })); // custom cat + 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 @@ -216,10 +315,36 @@ namespace Jackett.Test.Common.Models { var lhs = new TorznabCapabilitiesCategories(); var rhs = CreateTestDataset(); - lhs.Concat(rhs); - Assert.AreEqual(5, lhs.GetTorznabCategories().Count); // removed custom cats + var expected = new List + { + TorznabCatType.Movies.CopyWithoutSubCategories(), + TorznabCatType.Books.CopyWithoutSubCategories(), + TorznabCatType.Console.CopyWithoutSubCategories() + }; + expected[0].SubCategories.Add(TorznabCatType.MoviesSD.CopyWithoutSubCategories()); + expected[1].SubCategories.Add(TorznabCatType.BooksComics.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleXBox.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleWii.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, lhs.GetTorznabCategoryTree()); // removed custom cats Assert.AreEqual(0, lhs.GetTrackerCategories().Count); // removed tracker mapping + + lhs = CreateTestDataset(); + rhs = CreateTestDataset(); + lhs.Concat(rhs); + expected = new List + { + TorznabCatType.Movies.CopyWithoutSubCategories(), + TorznabCatType.Books.CopyWithoutSubCategories(), + TorznabCatType.Console.CopyWithoutSubCategories(), + new TorznabCategory(100044, "Console/Xbox_c"), + new TorznabCategory(100040, "Console/Xbox_c2") + }; + expected[0].SubCategories.Add(TorznabCatType.MoviesSD.CopyWithoutSubCategories()); + expected[1].SubCategories.Add(TorznabCatType.BooksComics.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleXBox.CopyWithoutSubCategories()); + expected[2].SubCategories.Add(TorznabCatType.ConsoleWii.CopyWithoutSubCategories()); + TestCategories.CompareCategoryTrees(expected, lhs.GetTorznabCategoryTree()); // check there are not duplicates } private static TorznabCapabilitiesCategories CreateTestDataset() diff --git a/src/Jackett.Test/Common/Models/TorznabCapabilitiesTests.cs b/src/Jackett.Test/Common/Models/TorznabCapabilitiesTests.cs index 36f03e09d..815fdc05e 100644 --- a/src/Jackett.Test/Common/Models/TorznabCapabilitiesTests.cs +++ b/src/Jackett.Test/Common/Models/TorznabCapabilitiesTests.cs @@ -42,7 +42,7 @@ namespace Jackett.Test.Common.Models Assert.False(torznabCaps.BookSearchTitleAvailable); Assert.False(torznabCaps.BookSearchAuthorAvailable); - Assert.IsEmpty(torznabCaps.Categories.GetTorznabCategories()); + Assert.IsEmpty(torznabCaps.Categories.GetTorznabCategoryTree()); Assert.IsEmpty(torznabCaps.Categories.GetTrackerCategories()); } @@ -405,40 +405,32 @@ namespace Jackett.Test.Common.Models Assert.AreEqual("q,title,author", xDocumentSearching?.Element("book-search")?.Attribute("supportedParams")?.Value); // test categories - torznabCaps = new TorznabCapabilities(); - torznabCaps.Categories.AddCategoryMapping("c1", TorznabCatType.MoviesSD); // child category + torznabCaps = new TorznabCapabilities(); // child category + torznabCaps.Categories.AddCategoryMapping("c1", TorznabCatType.MoviesSD); xDocument = torznabCaps.GetXDocument(); var xDocumentCategories = xDocument.Root?.Element("categories")?.Elements("category").ToList(); Assert.AreEqual(1, xDocumentCategories?.Count); - Assert.AreEqual(TorznabCatType.MoviesSD.ID.ToString(), xDocumentCategories?.First().Attribute("id")?.Value); - Assert.AreEqual(TorznabCatType.MoviesSD.Name, xDocumentCategories?.First().Attribute("name")?.Value); + Assert.AreEqual(TorznabCatType.Movies.ID.ToString(), xDocumentCategories?[0].Attribute("id")?.Value); + Assert.AreEqual(TorznabCatType.Movies.Name, xDocumentCategories?[0].Attribute("name")?.Value); + var xDocumentSubCategories = xDocumentCategories?[0]?.Elements("subcat").ToList(); + Assert.AreEqual(1, xDocumentSubCategories?.Count); + Assert.AreEqual(TorznabCatType.MoviesSD.ID.ToString(), xDocumentSubCategories?[0].Attribute("id")?.Value); + Assert.AreEqual(TorznabCatType.MoviesSD.Name, xDocumentSubCategories?[0].Attribute("name")?.Value); - // TODO: child category is duplicated. should we add just parent and child without other subcats? - torznabCaps = new TorznabCapabilities(); - torznabCaps.Categories.AddCategoryMapping("c1", TorznabCatType.Movies); // parent and child category + torznabCaps = new TorznabCapabilities(); // parent (with description generates a custom cat) and child category + torznabCaps.Categories.AddCategoryMapping("1", TorznabCatType.Movies, "Classic Movies"); torznabCaps.Categories.AddCategoryMapping("c2", TorznabCatType.MoviesSD); xDocument = torznabCaps.GetXDocument(); xDocumentCategories = xDocument.Root?.Element("categories")?.Elements("category").ToList(); Assert.AreEqual(2, xDocumentCategories?.Count); - Assert.AreEqual(TorznabCatType.Movies.ID.ToString(), xDocumentCategories?.First().Attribute("id")?.Value); - Assert.AreEqual(TorznabCatType.Movies.Name, xDocumentCategories?.First().Attribute("name")?.Value); - Assert.AreEqual(TorznabCatType.MoviesSD.ID.ToString(), xDocumentCategories?[1].Attribute("id")?.Value); - Assert.AreEqual(TorznabCatType.MoviesSD.Name, xDocumentCategories?[1].Attribute("name")?.Value); - var xDocumentSubCategories = xDocumentCategories?.First()?.Elements("subcat").ToList(); - Assert.AreEqual(9, xDocumentSubCategories?.Count); - Assert.AreEqual(TorznabCatType.MoviesForeign.ID.ToString(), xDocumentSubCategories?.First().Attribute("id")?.Value); - Assert.AreEqual(TorznabCatType.MoviesForeign.Name, xDocumentSubCategories?.First().Attribute("name")?.Value); - - torznabCaps = new TorznabCapabilities(); - torznabCaps.Categories.AddCategoryMapping("c1", new TorznabCategory(100001, "CustomCat")); // custom category - torznabCaps.Categories.AddCategoryMapping("c2", TorznabCatType.MoviesSD); - xDocument = torznabCaps.GetXDocument(); - xDocumentCategories = xDocument.Root?.Element("categories")?.Elements("category").ToList(); - Assert.AreEqual(2, xDocumentCategories?.Count); - Assert.AreEqual("100001", xDocumentCategories?[0].Attribute("id")?.Value); // custom cats are first in the list - Assert.AreEqual("CustomCat", xDocumentCategories?[0].Attribute("name")?.Value); - Assert.AreEqual(TorznabCatType.MoviesSD.ID.ToString(), xDocumentCategories?[1].Attribute("id")?.Value); - Assert.AreEqual(TorznabCatType.MoviesSD.Name, xDocumentCategories?[1].Attribute("name")?.Value); + Assert.AreEqual(TorznabCatType.Movies.ID.ToString(), xDocumentCategories?[0].Attribute("id")?.Value); + Assert.AreEqual(TorznabCatType.Movies.Name, xDocumentCategories?[0].Attribute("name")?.Value); + xDocumentSubCategories = xDocumentCategories?[0]?.Elements("subcat").ToList(); + Assert.AreEqual(1, xDocumentSubCategories?.Count); + Assert.AreEqual(TorznabCatType.MoviesSD.ID.ToString(), xDocumentSubCategories?[0].Attribute("id")?.Value); + Assert.AreEqual(TorznabCatType.MoviesSD.Name, xDocumentSubCategories?[0].Attribute("name")?.Value); + Assert.AreEqual("100001", xDocumentCategories?[1].Attribute("id")?.Value); // custom cats are first in the list + Assert.AreEqual("Classic Movies", xDocumentCategories?[1].Attribute("name")?.Value); } [Test] @@ -450,15 +442,17 @@ namespace Jackett.Test.Common.Models // test Torznab caps (XML) => more in Common.Model.TorznabCapabilitiesTests var xDocument = torznabCaps.GetXDocument(); var xDocumentCategories = xDocument.Root?.Element("categories")?.Elements("category").ToList(); - Assert.AreEqual(7, xDocumentCategories?.Count); - Assert.AreEqual("100044", xDocumentCategories?[0].Attribute("id")?.Value); - Assert.AreEqual("100045", xDocumentCategories?[1].Attribute("id")?.Value); - Assert.AreEqual("1030", xDocumentCategories?[2].Attribute("id")?.Value); - Assert.AreEqual("1040", xDocumentCategories?[3].Attribute("id")?.Value); - Assert.AreEqual("2000", xDocumentCategories?[4].Attribute("id")?.Value); // Movies - Assert.AreEqual("2030", xDocumentCategories?[5].Attribute("id")?.Value); - Assert.AreEqual("7030", xDocumentCategories?[6].Attribute("id")?.Value); - Assert.AreEqual(9, xDocumentCategories?[4]?.Elements("subcat").ToList().Count); // Movies + Assert.AreEqual(5, xDocumentCategories?.Count); + Assert.AreEqual("1000", xDocumentCategories?[0].Attribute("id")?.Value); + Assert.AreEqual(2, xDocumentCategories?[0]?.Elements("subcat").ToList().Count); + Assert.AreEqual("2000", xDocumentCategories?[1].Attribute("id")?.Value); + Assert.AreEqual(1, xDocumentCategories?[1]?.Elements("subcat").ToList().Count); + Assert.AreEqual("7000", xDocumentCategories?[2].Attribute("id")?.Value); + Assert.AreEqual(1, xDocumentCategories?[2]?.Elements("subcat").ToList().Count); + Assert.AreEqual("100044", xDocumentCategories?[3].Attribute("id")?.Value); + Assert.AreEqual(0, xDocumentCategories?[3]?.Elements("subcat").ToList().Count); + Assert.AreEqual("100040", xDocumentCategories?[4].Attribute("id")?.Value); + Assert.AreEqual(0, xDocumentCategories?[4]?.Elements("subcat").ToList().Count); } [Test] @@ -473,7 +467,7 @@ namespace Jackett.Test.Common.Models Assert.IsEmpty(res.MovieSearchParams); Assert.IsEmpty(res.MusicSearchParams); Assert.IsEmpty(res.BookSearchParams); - Assert.IsEmpty(res.Categories.GetTorznabCategories()); + Assert.IsEmpty(res.Categories.GetTorznabCategoryTree()); torznabCaps1 = new TorznabCapabilities { @@ -502,7 +496,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.GetTorznabCategories().Count == 3); // only CustomCat2 is removed + Assert.True(res.Categories.GetTorznabCategoryTree().Count == 3); // only CustomCat2 is removed } } } diff --git a/src/Jackett.Test/TestHelpers/TestCategories.cs b/src/Jackett.Test/TestHelpers/TestCategories.cs index 2b96490f4..c4e3267a6 100644 --- a/src/Jackett.Test/TestHelpers/TestCategories.cs +++ b/src/Jackett.Test/TestHelpers/TestCategories.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using Jackett.Common.Models; +using NUnit.Framework; namespace Jackett.Test.TestHelpers { @@ -11,12 +13,24 @@ namespace Jackett.Test.TestHelpers // - with and without description // - parent and child categories // - custom categories are not added automatically but they are created from other categories automatically + // - categories and subcategories are unsorted to test the sort when required 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"); + tcc.AddCategoryMapping("40", TorznabCatType.ConsoleXBox, "Console/Xbox_c2"); + } + + public static void CompareCategoryTrees(List tree1, List tree2) + { + Assert.AreEqual(tree1.Count, tree2.Count); + for (var i = 0; i < tree1.Count; i++) + { + Assert.AreEqual(tree1[i].ID, tree2[i].ID); + Assert.AreEqual(tree1[i].Name, tree2[i].Name); + CompareCategoryTrees(tree1[i].SubCategories, tree2[i].SubCategories); + } } } }