From 9eebc227c5cfb3195ccfc3f68cad4080b6e266ef Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Fri, 17 Jun 2022 15:24:43 -0500 Subject: [PATCH] refactor(radarr): Parse guide data into objects This sets the groundwork for making Radarr guide data available for other usages beyond syncing to Radarr, such as spitting out information to the console. --- src/TrashLib.TestLibrary/NewCf.cs | 46 +++++++ .../Radarr/CustomFormat/CachePersisterTest.cs | 6 +- .../LocalRepoCustomFormatJsonParserTest.cs | 24 +++- .../Processors/GuideProcessorTest.cs | 32 ++--- .../Processors/GuideSteps/ConfigStepTest.cs | 36 ++---- .../GuideSteps/CustomFormatStepTest.cs | 120 +++++++----------- .../GuideSteps/QualityProfileStepTest.cs | 9 +- .../CustomFormatApiPersistenceStepTest.cs | 7 +- .../JsonTransactionStepTest.cs | 18 ++- .../QualityProfileApiPersistenceStepTest.cs | 33 ++--- .../CustomFormat/Guide/IRadarrGuideService.cs | 4 +- .../Guide/LocalRepoCustomFormatJsonParser.cs | 64 ++++++++-- .../CustomFormat/Models/CustomFormatData.cs | 11 ++ .../Models/ProcessedCustomFormatData.cs | 16 +-- .../CustomFormat/Processors/GuideProcessor.cs | 4 +- .../Processors/GuideSteps/CustomFormatStep.cs | 30 ++--- .../GuideSteps/ICustomFormatStep.cs | 2 +- 17 files changed, 254 insertions(+), 208 deletions(-) create mode 100644 src/TrashLib.TestLibrary/NewCf.cs create mode 100644 src/TrashLib/Radarr/CustomFormat/Models/CustomFormatData.cs diff --git a/src/TrashLib.TestLibrary/NewCf.cs b/src/TrashLib.TestLibrary/NewCf.cs new file mode 100644 index 00000000..acd44bbb --- /dev/null +++ b/src/TrashLib.TestLibrary/NewCf.cs @@ -0,0 +1,46 @@ +using Newtonsoft.Json.Linq; +using TrashLib.Radarr.CustomFormat.Models; +using TrashLib.Radarr.CustomFormat.Models.Cache; + +namespace TrashLib.TestLibrary; + +public static class NewCf +{ + public static CustomFormatData Data(string name, string trashId, int? score = null) + { + var json = JObject.Parse($"{{'name':'{name}'}}"); + return new CustomFormatData(name, trashId, score, new JObject(json)); + } + + public static ProcessedCustomFormatData Processed(string name, string trashId, int? score = null) + { + return new ProcessedCustomFormatData(Data(name, trashId, score)); + } + + public static ProcessedCustomFormatData Processed(string name, string trashId, int? score, JObject json) + { + return new ProcessedCustomFormatData(new CustomFormatData(name, trashId, score, json)); + } + + public static ProcessedCustomFormatData Processed(string name, string trashId, JObject json) + { + return Processed(name, trashId, null, json); + } + + public static ProcessedCustomFormatData Processed(string name, string trashId, TrashIdMapping cacheEntry) + { + return new ProcessedCustomFormatData(Data(name, trashId)) + { + CacheEntry = cacheEntry + }; + } + + public static ProcessedCustomFormatData Processed(string name, string trashId, JObject json, + TrashIdMapping? cacheEntry) + { + return new ProcessedCustomFormatData(new CustomFormatData(name, trashId, null, json)) + { + CacheEntry = cacheEntry + }; + } +} diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/CachePersisterTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/CachePersisterTest.cs index 2720545e..0a8fa6b8 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/CachePersisterTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/CachePersisterTest.cs @@ -31,7 +31,8 @@ public class CachePersisterTest private static ProcessedCustomFormatData QuickMakeCf(string cfName, string trashId, int cfId) { - return new ProcessedCustomFormatData(cfName, trashId, new JObject()) + var cf = new CustomFormatData(cfName, trashId, null, new JObject()); + return new ProcessedCustomFormatData(cf) { CacheEntry = new TrashIdMapping(trashId, cfName) {CustomFormatId = cfId} }; @@ -127,7 +128,8 @@ public class CachePersisterTest var customFormatData = new List { - new("", "trashid", new JObject()) {CacheEntry = new TrashIdMapping("trashid", "cfname", 10)} + new(new CustomFormatData("", "trashid", null, new JObject())) + {CacheEntry = new TrashIdMapping("trashid", "cfname", 10)} }; ctx.Persister.Update(customFormatData); diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParserTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParserTest.cs index 89bca6b9..b9878893 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParserTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParserTest.cs @@ -1,3 +1,5 @@ +using System.IO.Abstractions; +using System.IO.Abstractions.Extensions; using System.IO.Abstractions.TestingHelpers; using AutoFixture.NUnit3; using FluentAssertions; @@ -5,6 +7,7 @@ using NSubstitute; using NUnit.Framework; using TestLibrary.AutoFixture; using TrashLib.Radarr.CustomFormat.Guide; +using TrashLib.TestLibrary; namespace TrashLib.Tests.Radarr.CustomFormat.Guide; @@ -15,15 +18,24 @@ public class LocalRepoCustomFormatJsonParserTest [Test, AutoMockData] public void Get_custom_format_json_works( [Frozen] IAppPaths paths, - [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fileSystem, + [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, LocalRepoCustomFormatJsonParser sut) { - paths.RepoDirectory.Returns(""); - fileSystem.AddFile("docs/json/radarr/first.json", new MockFileData("first")); - fileSystem.AddFile("docs/json/radarr/second.json", new MockFileData("second")); + var jsonDir = fs.CurrentDirectory() + .SubDirectory("docs") + .SubDirectory("json") + .SubDirectory("radarr"); - var results = sut.GetCustomFormatJson(); + paths.RepoDirectory.Returns(fs.CurrentDirectory().FullName); + fs.AddFile(jsonDir.File("first.json").FullName, new MockFileData("{'name':'first','trash_id':'1'}")); + fs.AddFile(jsonDir.File("second.json").FullName, new MockFileData("{'name':'second','trash_id':'2'}")); - results.Should().BeEquivalentTo("first", "second"); + var results = sut.GetCustomFormatData(); + + results.Should().BeEquivalentTo(new[] + { + NewCf.Data("first", "1"), + NewCf.Data("second", "2") + }); } } diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideProcessorTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideProcessorTest.cs index ce476011..6b2f995d 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideProcessorTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideProcessorTest.cs @@ -34,6 +34,9 @@ public class GuideProcessorTest public ResourceDataReader Data { get; } + public CustomFormatData ReadCustomFormat(string textFile) => + LocalRepoCustomFormatJsonParser.ParseCustomFormatData(ReadText(textFile)); + public string ReadText(string textFile) => Data.ReadData(textFile); public JObject ReadJson(string jsonFile) => JObject.Parse(ReadText(jsonFile)); } @@ -47,12 +50,12 @@ public class GuideProcessorTest var guideProcessor = new GuideProcessor(guideService, () => new TestGuideProcessorSteps()); // simulate guide data - guideService.GetCustomFormatJson().Returns(new[] + guideService.GetCustomFormatData().Returns(new[] { - ctx.ReadText("ImportableCustomFormat1.json"), - ctx.ReadText("ImportableCustomFormat2.json"), - ctx.ReadText("NoScore.json"), - ctx.ReadText("WontBeInConfig.json") + ctx.ReadCustomFormat("ImportableCustomFormat1.json"), + ctx.ReadCustomFormat("ImportableCustomFormat2.json"), + ctx.ReadCustomFormat("NoScore.json"), + ctx.ReadCustomFormat("WontBeInConfig.json") }); // Simulate user config in YAML @@ -82,21 +85,14 @@ public class GuideProcessorTest var expectedProcessedCustomFormatData = new List { - new("Surround Sound", "43bb5f09c79641e7a22e48d440bd8868", ctx.ReadJson( - "ImportableCustomFormat1_Processed.json")) - { - Score = 500 - }, - new("DTS-HD/DTS:X", "4eb3c272d48db8ab43c2c85283b69744", ctx.ReadJson( - "ImportableCustomFormat2_Processed.json")) - { - Score = 480 - }, - new("No Score", "abc", JObject.FromObject(new {name = "No Score"})) + NewCf.Processed("Surround Sound", "43bb5f09c79641e7a22e48d440bd8868", 500, + ctx.ReadJson("ImportableCustomFormat1_Processed.json")), + NewCf.Processed("DTS-HD/DTS:X", "4eb3c272d48db8ab43c2c85283b69744", 480, + ctx.ReadJson("ImportableCustomFormat2_Processed.json")), + NewCf.Processed("No Score", "abc") }; - guideProcessor.ProcessedCustomFormats.Should() - .BeEquivalentTo(expectedProcessedCustomFormatData, op => op.Using(new JsonEquivalencyStep())); + guideProcessor.ProcessedCustomFormats.Should().BeEquivalentTo(expectedProcessedCustomFormatData); guideProcessor.ConfigData.Should() .BeEquivalentTo(new List diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/ConfigStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/ConfigStepTest.cs index b7e538bd..fd8d7ee7 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/ConfigStepTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/ConfigStepTest.cs @@ -5,6 +5,7 @@ using TrashLib.Radarr.Config; using TrashLib.Radarr.CustomFormat.Models; using TrashLib.Radarr.CustomFormat.Models.Cache; using TrashLib.Radarr.CustomFormat.Processors.GuideSteps; +using TrashLib.TestLibrary; namespace TrashLib.Tests.Radarr.CustomFormat.Processors.GuideSteps; @@ -17,14 +18,8 @@ public class ConfigStepTest { var testProcessedCfs = new List { - new("name1", "id1", JObject.FromObject(new {name = "name1"})) - { - Score = 100 - }, - new("name3", "id3", JObject.FromObject(new {name = "name3"})) - { - CacheEntry = new TrashIdMapping("id3", "name1") - } + NewCf.Processed("name1", "id1", 100), + NewCf.Processed("name3", "id3", new TrashIdMapping("id3", "name1")) }; var testConfig = new CustomFormatConfig[] @@ -56,8 +51,8 @@ public class ConfigStepTest { var testProcessedCfs = new List { - new("name1", "", new JObject()), - new("name2", "", new JObject()) + NewCf.Processed("name1", ""), + NewCf.Processed("name2", "") }; var testConfig = new CustomFormatConfig[] @@ -78,7 +73,7 @@ public class ConfigStepTest { CustomFormats = new List { - new("name1", "", new JObject()) + NewCf.Processed("name1", "") } } }, op => op @@ -91,8 +86,8 @@ public class ConfigStepTest { var testProcessedCfs = new List { - new("name1", "", new JObject()), - new("name2", "", new JObject()) + NewCf.Processed("name1", ""), + NewCf.Processed("name2", "") }; var testConfig = new CustomFormatConfig[] @@ -115,7 +110,7 @@ public class ConfigStepTest { CustomFormats = new List { - new("name1", "", new JObject()) + NewCf.Processed("name1", "") } } }, op => op @@ -128,7 +123,7 @@ public class ConfigStepTest { var testProcessedCfs = new List { - new("name1", "id1", new JObject()) + NewCf.Processed("name1", "id1") }; var testConfig = new CustomFormatConfig[] @@ -158,7 +153,7 @@ public class ConfigStepTest { var testProcessedCfs = new List { - new("name1", "id1", new JObject()) + NewCf.Processed("name1", "id1") }; var testConfig = new CustomFormatConfig[] @@ -184,12 +179,9 @@ public class ConfigStepTest { var testProcessedCfs = new List { - new("name1", "id1", JObject.FromObject(new {name = "name1"})) - { - Score = 100 - }, - new("name3", "id3", JObject.FromObject(new {name = "name3"})), - new("name4", "id4", new JObject()) + NewCf.Processed("name1", "id1", 100), + NewCf.Processed("name3", "id3"), + NewCf.Processed("name4", "id4") }; var testConfig = new CustomFormatConfig[] diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs index f0f32508..7f277e25 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs @@ -1,6 +1,5 @@ using System.Collections.ObjectModel; using FluentAssertions; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NUnit.Framework; using TestLibrary.FluentAssertions; @@ -8,6 +7,7 @@ using TrashLib.Radarr.Config; using TrashLib.Radarr.CustomFormat.Models; using TrashLib.Radarr.CustomFormat.Models.Cache; using TrashLib.Radarr.CustomFormat.Processors.GuideSteps; +using TrashLib.TestLibrary; namespace TrashLib.Tests.Radarr.CustomFormat.Processors.GuideSteps; @@ -17,23 +17,11 @@ public class CustomFormatStepTest { private class Context { - public List TestGuideData { get; } = new() + public List TestGuideData { get; } = new() { - JsonConvert.SerializeObject(new - { - trash_id = "id1", - name = "name1" - }, Formatting.Indented), - JsonConvert.SerializeObject(new - { - trash_id = "id2", - name = "name2" - }, Formatting.Indented), - JsonConvert.SerializeObject(new - { - trash_id = "id3", - name = "name3" - }, Formatting.Indented) + NewCf.Data("name1", "id1"), + NewCf.Data("name2", "id2"), + NewCf.Data("name3", "id3") }; } @@ -56,13 +44,9 @@ public class CustomFormatStepTest } }; - var testGuideData = new List + var testGuideData = new List { - JsonConvert.SerializeObject(new - { - trash_id = "id1", - name = variableCfName - }, Formatting.Indented) + NewCf.Data(variableCfName, "id1") }; var processor = new CustomFormatStep(); @@ -72,21 +56,17 @@ public class CustomFormatStepTest processor.CustomFormatsWithOutdatedNames.Should().HaveCount(outdatedCount); processor.DeletedCustomFormatsInCache.Should().BeEmpty(); processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List - { - new(variableCfName, "id1", JObject.FromObject(new {name = variableCfName})) - { - CacheEntry = testCache.TrashIdMappings[0] - } - }, - op => op.Using(new JsonEquivalencyStep())); + { + NewCf.Processed(variableCfName, "id1", testCache.TrashIdMappings[0]) + }); } [Test] public void Cache_entry_is_not_set_when_id_is_different() { - var guideData = new List + var guideData = new List { - @"{'name': 'name1', 'trash_id': 'id1'}" + NewCf.Data("name1", "id1") }; var testConfig = new List @@ -108,15 +88,11 @@ public class CustomFormatStepTest processor.DuplicatedCustomFormats.Should().BeEmpty(); processor.CustomFormatsWithOutdatedNames.Should().BeEmpty(); processor.DeletedCustomFormatsInCache.Count.Should().Be(1); - processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List + processor.ProcessedCustomFormats.Should() + .BeEquivalentTo(new List { - new("name1", "id1", JObject.FromObject(new {name = "name1"})) - { - Score = null, - CacheEntry = null - } - }, - op => op.Using(new JsonEquivalencyStep())); + NewCf.Processed("name1", "id1") + }); } [Test] @@ -134,12 +110,12 @@ public class CustomFormatStepTest processor.DuplicatedCustomFormats.Should().BeEmpty(); processor.CustomFormatsWithOutdatedNames.Should().BeEmpty(); processor.DeletedCustomFormatsInCache.Should().BeEmpty(); - processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List + processor.ProcessedCustomFormats.Should() + .BeEquivalentTo(new List { - new("name1", "id1", JObject.FromObject(new {name = "name1"})) {Score = null}, - new("name3", "id3", JObject.FromObject(new {name = "name3"})) {Score = null} - }, - op => op.Using(new JsonEquivalencyStep())); + NewCf.Processed("name1", "id1"), + NewCf.Processed("name3", "id3") + }); } [Test] @@ -160,9 +136,9 @@ public class CustomFormatStepTest processor.DeletedCustomFormatsInCache.Should().BeEmpty(); processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List { - new("name1", "id1", JObject.FromObject(new {name = "name1"})) {Score = null}, - new("name2", "id2", JObject.FromObject(new {name = "name2"})) {Score = null}, - new("name3", "id3", JObject.FromObject(new {name = "name3"})) {Score = null} + NewCf.Processed("name1", "id1"), + NewCf.Processed("name2", "id2"), + NewCf.Processed("name3", "id3") }, op => op.Using(new JsonEquivalencyStep())); } @@ -170,9 +146,9 @@ public class CustomFormatStepTest [Test] public void Custom_format_is_deleted_if_in_config_and_cache_but_not_in_guide() { - var guideData = new List + var guideData = new List { - @"{'name': 'name1', 'trash_id': 'id1'}" + NewCf.Data("name1", "id1") }; var testConfig = new List @@ -193,10 +169,9 @@ public class CustomFormatStepTest processor.DeletedCustomFormatsInCache.Should() .BeEquivalentTo(new[] {new TrashIdMapping("id1000", "name1")}); processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List - { - new("name1", "id1", JObject.Parse(@"{'name': 'name1'}")) - }, - op => op.Using(new JsonEquivalencyStep())); + { + NewCf.Processed("name1", "id1") + }); } [Test] @@ -207,9 +182,9 @@ public class CustomFormatStepTest TrashIdMappings = new Collection {new("id1", "3D", 9)} }; - var guideCfs = new List + var guideCfs = new List { - "{'name': '3D', 'trash_id': 'id1'}" + new("3D", "id1", null, new JObject()) }; var processor = new CustomFormatStep(); @@ -224,9 +199,9 @@ public class CustomFormatStepTest [Test] public void Custom_format_name_in_cache_is_updated_if_renamed_in_guide_and_config() { - var guideData = new List + var guideData = new List { - @"{'name': 'name2', 'trash_id': 'id1'}" + new("name2", "id1", null, new JObject()) }; var testConfig = new List @@ -253,10 +228,10 @@ public class CustomFormatStepTest [Test] public void Duplicates_are_recorded_and_removed_from_processed_custom_formats_list() { - var guideData = new List + var guideData = new List { - @"{'name': 'name1', 'trash_id': 'id1'}", - @"{'name': 'name1', 'trash_id': 'id2'}" + NewCf.Data("name1", "id1"), + NewCf.Data("name1", "id2") }; var testConfig = new List @@ -272,8 +247,8 @@ public class CustomFormatStepTest .ContainKey("name1").WhoseValue.Should() .BeEquivalentTo(new List { - new("name1", "id1", JObject.Parse(@"{'name': 'name1'}")), - new("name1", "id2", JObject.Parse(@"{'name': 'name1'}")) + NewCf.Processed("name1", "id1"), + NewCf.Processed("name1", "id2") }); processor.CustomFormatsWithOutdatedNames.Should().BeEmpty(); processor.DeletedCustomFormatsInCache.Should().BeEmpty(); @@ -297,7 +272,7 @@ public class CustomFormatStepTest processor.DeletedCustomFormatsInCache.Should().BeEmpty(); processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List { - new("name1", "id1", JObject.FromObject(new {name = "name1"})) + NewCf.Processed("name1", "id1") }, op => op.Using(new JsonEquivalencyStep())); } @@ -305,10 +280,10 @@ public class CustomFormatStepTest [Test] public void Match_custom_format_using_trash_id() { - var guideData = new List + var guideData = new List { - @"{'name': 'name1', 'trash_id': 'id1'}", - @"{'name': 'name2', 'trash_id': 'id2'}" + NewCf.Data("name1", "id1"), + NewCf.Data("name2", "id2") }; var testConfig = new List @@ -325,7 +300,7 @@ public class CustomFormatStepTest processor.ProcessedCustomFormats.Should() .BeEquivalentTo(new List { - new("name2", "id2", JObject.FromObject(new {name = "name2"})) + NewCf.Processed("name2", "id2") }); } @@ -350,9 +325,9 @@ public class CustomFormatStepTest [Test] public void Score_from_json_takes_precedence_over_score_from_guide() { - var guideData = new List + var guideData = new List { - @"{'name': 'name1', 'trash_id': 'id1', 'trash_score': 100}" + NewCf.Data("name1", "id1", 100) }; var testConfig = new List @@ -376,10 +351,7 @@ public class CustomFormatStepTest processor.ProcessedCustomFormats.Should() .BeEquivalentTo(new List { - new("name1", "id1", JObject.FromObject(new {name = "name1"})) - { - Score = 100 - } + NewCf.Processed("name1", "id1", 100) }, op => op.Using(new JsonEquivalencyStep())); } diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs index e618fb5e..17516c24 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using Newtonsoft.Json.Linq; using NUnit.Framework; using TrashLib.Radarr.Config; using TrashLib.Radarr.CustomFormat.Models; @@ -21,7 +20,7 @@ public class QualityProfileStepTest { CustomFormats = new List { - new("name1", "id1", new JObject()) {Score = null} + NewCf.Processed("name1", "id1") }, QualityProfiles = new List { @@ -46,7 +45,7 @@ public class QualityProfileStepTest { CustomFormats = new List { - new("", "id1", new JObject()) {Score = 100} + NewCf.Processed("", "id1", 100) }, QualityProfiles = new List { @@ -75,7 +74,7 @@ public class QualityProfileStepTest { CustomFormats = new List { - new("", "id1", new JObject()) {Score = 100} + NewCf.Processed("", "id1", 100) }, QualityProfiles = new List { @@ -110,7 +109,7 @@ public class QualityProfileStepTest { CustomFormats = new List { - new("name1", "id1", new JObject()) {Score = 0} + NewCf.Processed("name1", "id1", 0) }, QualityProfiles = new List { diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs index c8da8fe1..604a08c7 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json.Linq; using NSubstitute; using NUnit.Framework; using TrashLib.Radarr.CustomFormat.Api; using TrashLib.Radarr.CustomFormat.Models; using TrashLib.Radarr.CustomFormat.Models.Cache; using TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps; +using TrashLib.TestLibrary; namespace TrashLib.Tests.Radarr.CustomFormat.Processors.PersistenceSteps; @@ -14,10 +14,7 @@ public class CustomFormatApiPersistenceStepTest { private static ProcessedCustomFormatData QuickMakeCf(string cfName, string trashId, int cfId) { - return new ProcessedCustomFormatData(cfName, trashId, new JObject()) - { - CacheEntry = new TrashIdMapping(trashId, cfName) {CustomFormatId = cfId} - }; + return NewCf.Processed(cfName, trashId, new TrashIdMapping(trashId, cfName) {CustomFormatId = cfId}); } [Test] diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/JsonTransactionStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/JsonTransactionStepTest.cs index 34a5f76d..e54d93fe 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/JsonTransactionStepTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/JsonTransactionStepTest.cs @@ -6,6 +6,7 @@ using TestLibrary.FluentAssertions; using TrashLib.Radarr.CustomFormat.Models; using TrashLib.Radarr.CustomFormat.Models.Cache; using TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps; +using TrashLib.TestLibrary; /* Sample Custom Format response from Radarr API { @@ -71,7 +72,7 @@ public class JsonTransactionStepTest var guideCfs = new List { - new(guideCfName, "", guideCfData) {CacheEntry = cacheEntry} + NewCf.Processed(guideCfName, "", guideCfData, cacheEntry) }; var processor = new JsonTransactionStep(); @@ -173,12 +174,9 @@ public class JsonTransactionStepTest var radarrCfs = JsonConvert.DeserializeObject>(radarrCfData); var guideCfs = new List { - new("created", "", guideCfData![0]), - new("updated_different_name", "", guideCfData[1]) - { - CacheEntry = new TrashIdMapping("", "", 2) - }, - new("no_change", "", guideCfData[2]) + NewCf.Processed("created", "", guideCfData![0]), + NewCf.Processed("updated_different_name", "", guideCfData[1], new TrashIdMapping("", "", 2)), + NewCf.Processed("no_change", "", guideCfData[2]) }; var processor = new JsonTransactionStep(); @@ -289,7 +287,7 @@ public class JsonTransactionStepTest var guideCfs = new List { - new("updated", "", guideCfData) {CacheEntry = new TrashIdMapping("", "") {CustomFormatId = 1}} + NewCf.Processed("updated", "", guideCfData, new TrashIdMapping("", "") {CustomFormatId = 1}) }; var radarrCfs = JsonConvert.DeserializeObject>(radarrCfData); @@ -408,8 +406,8 @@ public class JsonTransactionStepTest var radarrCfs = JsonConvert.DeserializeObject>(radarrCfData); var guideCfs = new List { - new("updated", "", guideCfData![0]), - new("no_change", "", guideCfData[1]) + NewCf.Processed("updated", "", guideCfData![0]), + NewCf.Processed("no_change", "", guideCfData[1]) }; var processor = new JsonTransactionStep(); diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs index fec0f134..8940cce5 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs @@ -47,11 +47,8 @@ public class QualityProfileApiPersistenceStepTest var cfScores = new Dictionary { { - "profile1", CfTestUtils.NewMapping( - new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) - { - CacheEntry = new TrashIdMapping("", "") {CustomFormatId = 4} - }, 100)) + "profile1", CfTestUtils.NewMapping(new FormatMappingEntry( + NewCf.Processed("", "", new TrashIdMapping("", "") {CustomFormatId = 4}), 100)) } }; @@ -112,10 +109,7 @@ public class QualityProfileApiPersistenceStepTest { { "profile1", CfTestUtils.NewMappingWithReset( - new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) - { - CacheEntry = new TrashIdMapping("", "", 2) - }, 100)) + new FormatMappingEntry(NewCf.Processed("", "", new TrashIdMapping("", "", 2)), 100)) } }; @@ -188,21 +182,12 @@ public class QualityProfileApiPersistenceStepTest { { "profile1", CfTestUtils.NewMapping( - new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) - { - // First match by ID - CacheEntry = new TrashIdMapping("", "", 4) - }, 100), - new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) - { - // Should NOT match because we do not use names to assign scores - CacheEntry = new TrashIdMapping("", "BR-DISK") - }, 101), - new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) - { - // Second match by ID - CacheEntry = new TrashIdMapping("", "", 1) - }, 102)) + // First match by ID + new FormatMappingEntry(NewCf.Processed("", "", new TrashIdMapping("", "", 4)), 100), + // Should NOT match because we do not use names to assign scores + new FormatMappingEntry(NewCf.Processed("", "", new TrashIdMapping("", "BR-DISK")), 101), + // Second match by ID + new FormatMappingEntry(NewCf.Processed("", "", new TrashIdMapping("", "", 1)), 102)) } }; diff --git a/src/TrashLib/Radarr/CustomFormat/Guide/IRadarrGuideService.cs b/src/TrashLib/Radarr/CustomFormat/Guide/IRadarrGuideService.cs index c378a9e4..f8b87dad 100644 --- a/src/TrashLib/Radarr/CustomFormat/Guide/IRadarrGuideService.cs +++ b/src/TrashLib/Radarr/CustomFormat/Guide/IRadarrGuideService.cs @@ -1,6 +1,8 @@ +using TrashLib.Radarr.CustomFormat.Models; + namespace TrashLib.Radarr.CustomFormat.Guide; public interface IRadarrGuideService { - IEnumerable GetCustomFormatJson(); + IEnumerable GetCustomFormatData(); } diff --git a/src/TrashLib/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParser.cs b/src/TrashLib/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParser.cs index b74719fc..1097b553 100644 --- a/src/TrashLib/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParser.cs +++ b/src/TrashLib/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParser.cs @@ -1,24 +1,72 @@ using System.IO.Abstractions; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using Common.Extensions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Serilog; +using TrashLib.Radarr.CustomFormat.Models; namespace TrashLib.Radarr.CustomFormat.Guide; public class LocalRepoCustomFormatJsonParser : IRadarrGuideService { - private readonly IFileSystem _fileSystem; + private readonly IFileSystem _fs; private readonly IAppPaths _paths; + private readonly ILogger _log; - public LocalRepoCustomFormatJsonParser(IFileSystem fileSystem, IAppPaths paths) + public LocalRepoCustomFormatJsonParser(IFileSystem fs, IAppPaths paths, ILogger log) { - _fileSystem = fileSystem; + _fs = fs; _paths = paths; + _log = log; } - public IEnumerable GetCustomFormatJson() + public IEnumerable GetCustomFormatData() { - var jsonDir = Path.Combine(_paths.RepoDirectory, "docs/json/radarr"); - var tasks = _fileSystem.Directory.GetFiles(jsonDir, "*.json") - .Select(f => _fileSystem.File.ReadAllTextAsync(f)); + var jsonDir = _fs.DirectoryInfo.FromDirectoryName(_paths.RepoDirectory) + .SubDirectory("docs") + .SubDirectory("json") + .SubDirectory("radarr"); - return Task.WhenAll(tasks).Result; + return jsonDir.EnumerateFiles("*.json").ToObservable() + .Select(x => Observable.Defer(() => LoadJsonFromFile(x))) + .Merge(8) + .NotNull() + .ToEnumerable() + .ToList(); + } + + private IObservable LoadJsonFromFile(IFileInfo file) + { + return Observable.Using(file.OpenText, x => x.ReadToEndAsync().ToObservable()) + .Do(_ => _log.Debug("Parsing CF Json: {Name}", file.Name)) + .Select(ParseCustomFormatData) + .Catch((JsonException e) => + { + _log.Warning("Failed to parse JSON file: {File} ({Reason})", file.Name, e.Message); + return Observable.Empty(); + }); + } + + public static CustomFormatData ParseCustomFormatData(string guideData) + { + var obj = JObject.Parse(guideData); + + var name = obj.ValueOrThrow("name"); + var trashId = obj.ValueOrThrow("trash_id"); + int? finalScore = null; + + if (obj.TryGetValue("trash_score", out var score)) + { + finalScore = (int) score; + obj.Property("trash_score")?.Remove(); + } + + // Remove trash_id, it's metadata that is not meant for Radarr itself + // Radarr supposedly drops this anyway, but I prefer it to be removed. + obj.Property("trash_id")?.Remove(); + + return new CustomFormatData(name, trashId, finalScore, obj); } } diff --git a/src/TrashLib/Radarr/CustomFormat/Models/CustomFormatData.cs b/src/TrashLib/Radarr/CustomFormat/Models/CustomFormatData.cs new file mode 100644 index 00000000..18d67d2c --- /dev/null +++ b/src/TrashLib/Radarr/CustomFormat/Models/CustomFormatData.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace TrashLib.Radarr.CustomFormat.Models; + +public record CustomFormatData( + string Name, + string TrashId, + int? Score, + [property: JsonExtensionData] JObject ExtraJson +); diff --git a/src/TrashLib/Radarr/CustomFormat/Models/ProcessedCustomFormatData.cs b/src/TrashLib/Radarr/CustomFormat/Models/ProcessedCustomFormatData.cs index 90abfb6c..6f14754a 100644 --- a/src/TrashLib/Radarr/CustomFormat/Models/ProcessedCustomFormatData.cs +++ b/src/TrashLib/Radarr/CustomFormat/Models/ProcessedCustomFormatData.cs @@ -6,19 +6,19 @@ namespace TrashLib.Radarr.CustomFormat.Models; public class ProcessedCustomFormatData { - public ProcessedCustomFormatData(string name, string trashId, JObject json) + private readonly CustomFormatData _data; + + public ProcessedCustomFormatData(CustomFormatData data) { - Name = name; - TrashId = trashId; - Json = json; + _data = data; + Json = _data.ExtraJson; } - public string Name { get; } - public string TrashId { get; } - public int? Score { get; init; } + public string Name => _data.Name; + public string TrashId => _data.TrashId; + public int? Score => _data.Score; public JObject Json { get; set; } public TrashIdMapping? CacheEntry { get; set; } - public string CacheAwareName => CacheEntry?.CustomFormatName ?? Name; public void SetCache(int customFormatId) diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs index d18eeace..c264cdf7 100644 --- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs +++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs @@ -17,7 +17,7 @@ internal class GuideProcessor : IGuideProcessor { private readonly IRadarrGuideService _guideService; private readonly Func _stepsFactory; - private IList? _guideCustomFormatJson; + private IList? _guideCustomFormatJson; private IGuideProcessorSteps _steps; public GuideProcessor(IRadarrGuideService guideService, Func stepsFactory) @@ -55,7 +55,7 @@ internal class GuideProcessor : IGuideProcessor { if (_guideCustomFormatJson == null) { - _guideCustomFormatJson = _guideService.GetCustomFormatJson().ToList(); + _guideCustomFormatJson = _guideService.GetCustomFormatData().ToList(); } // Step 1: Process and filter the custom formats from the guide. diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs index ff9cee3a..f1f1e85c 100644 --- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs +++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs @@ -1,5 +1,4 @@ using Common.Extensions; -using Newtonsoft.Json.Linq; using TrashLib.Radarr.Config; using TrashLib.Radarr.CustomFormat.Models; using TrashLib.Radarr.CustomFormat.Models.Cache; @@ -18,8 +17,10 @@ internal class CustomFormatStep : ICustomFormatStep public IReadOnlyCollection DeletedCustomFormatsInCache => _deletedCustomFormatsInCache; public IDictionary> DuplicatedCustomFormats => _duplicatedCustomFormats; - public void Process(IEnumerable customFormatGuideData, - IReadOnlyCollection config, CustomFormatCache? cache) + public void Process( + IList customFormatGuideData, + IReadOnlyCollection config, + CustomFormatCache? cache) { var processedCfs = customFormatGuideData .Select(cf => ProcessCustomFormatData(cf, cache)) @@ -94,27 +95,12 @@ internal class CustomFormatStep : ICustomFormatStep _processedCustomFormats.RemoveAll(cf => DuplicatedCustomFormats.ContainsKey(cf.Name)); } - private static ProcessedCustomFormatData ProcessCustomFormatData(string guideData, CustomFormatCache? cache) + private static ProcessedCustomFormatData ProcessCustomFormatData(CustomFormatData cf, + CustomFormatCache? cache) { - var obj = JObject.Parse(guideData); - var name = obj.ValueOrThrow("name"); - var trashId = obj.ValueOrThrow("trash_id"); - int? finalScore = null; - - if (obj.TryGetValue("trash_score", out var score)) - { - finalScore = (int) score; - obj.Property("trash_score")?.Remove(); - } - - // Remove trash_id, it's metadata that is not meant for Radarr itself - // Radarr supposedly drops this anyway, but I prefer it to be removed by TrashUpdater - obj.Property("trash_id")?.Remove(); - - return new ProcessedCustomFormatData(name, trashId, obj) + return new ProcessedCustomFormatData(cf) { - Score = finalScore, - CacheEntry = cache?.TrashIdMappings.FirstOrDefault(c => c.TrashId == trashId) + CacheEntry = cache?.TrashIdMappings.FirstOrDefault(c => c.TrashId == cf.TrashId) }; } diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs index 5e39ac35..1eada57e 100644 --- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs +++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs @@ -11,6 +11,6 @@ public interface ICustomFormatStep IReadOnlyCollection<(string, string)> CustomFormatsWithOutdatedNames { get; } IDictionary> DuplicatedCustomFormats { get; } - void Process(IEnumerable customFormatGuideData, + void Process(IList customFormatGuideData, IReadOnlyCollection config, CustomFormatCache? cache); }