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.
pull/92/head
Robert Dailey 2 years ago
parent 87830303ae
commit 9eebc227c5

@ -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
};
}
}

@ -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<ProcessedCustomFormatData>
{
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);

@ -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")
});
}
}

@ -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<ProcessedCustomFormatData>
{
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<ProcessedConfigData>

@ -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<ProcessedCustomFormatData>
{
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<ProcessedCustomFormatData>
{
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<ProcessedCustomFormatData>
{
new("name1", "", new JObject())
NewCf.Processed("name1", "")
}
}
}, op => op
@ -91,8 +86,8 @@ public class ConfigStepTest
{
var testProcessedCfs = new List<ProcessedCustomFormatData>
{
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<ProcessedCustomFormatData>
{
new("name1", "", new JObject())
NewCf.Processed("name1", "")
}
}
}, op => op
@ -128,7 +123,7 @@ public class ConfigStepTest
{
var testProcessedCfs = new List<ProcessedCustomFormatData>
{
new("name1", "id1", new JObject())
NewCf.Processed("name1", "id1")
};
var testConfig = new CustomFormatConfig[]
@ -158,7 +153,7 @@ public class ConfigStepTest
{
var testProcessedCfs = new List<ProcessedCustomFormatData>
{
new("name1", "id1", new JObject())
NewCf.Processed("name1", "id1")
};
var testConfig = new CustomFormatConfig[]
@ -184,12 +179,9 @@ public class ConfigStepTest
{
var testProcessedCfs = new List<ProcessedCustomFormatData>
{
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[]

@ -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<string> TestGuideData { get; } = new()
public List<CustomFormatData> 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<string>
var testGuideData = new List<CustomFormatData>
{
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<ProcessedCustomFormatData>
{
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<string>
var guideData = new List<CustomFormatData>
{
@"{'name': 'name1', 'trash_id': 'id1'}"
NewCf.Data("name1", "id1")
};
var testConfig = new List<CustomFormatConfig>
@ -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<ProcessedCustomFormatData>
processor.ProcessedCustomFormats.Should()
.BeEquivalentTo(new List<ProcessedCustomFormatData>
{
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<ProcessedCustomFormatData>
processor.ProcessedCustomFormats.Should()
.BeEquivalentTo(new List<ProcessedCustomFormatData>
{
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<ProcessedCustomFormatData>
{
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<string>
var guideData = new List<CustomFormatData>
{
@"{'name': 'name1', 'trash_id': 'id1'}"
NewCf.Data("name1", "id1")
};
var testConfig = new List<CustomFormatConfig>
@ -193,10 +169,9 @@ public class CustomFormatStepTest
processor.DeletedCustomFormatsInCache.Should()
.BeEquivalentTo(new[] {new TrashIdMapping("id1000", "name1")});
processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List<ProcessedCustomFormatData>
{
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<TrashIdMapping> {new("id1", "3D", 9)}
};
var guideCfs = new List<string>
var guideCfs = new List<CustomFormatData>
{
"{'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<string>
var guideData = new List<CustomFormatData>
{
@"{'name': 'name2', 'trash_id': 'id1'}"
new("name2", "id1", null, new JObject())
};
var testConfig = new List<CustomFormatConfig>
@ -253,10 +228,10 @@ public class CustomFormatStepTest
[Test]
public void Duplicates_are_recorded_and_removed_from_processed_custom_formats_list()
{
var guideData = new List<string>
var guideData = new List<CustomFormatData>
{
@"{'name': 'name1', 'trash_id': 'id1'}",
@"{'name': 'name1', 'trash_id': 'id2'}"
NewCf.Data("name1", "id1"),
NewCf.Data("name1", "id2")
};
var testConfig = new List<CustomFormatConfig>
@ -272,8 +247,8 @@ public class CustomFormatStepTest
.ContainKey("name1").WhoseValue.Should()
.BeEquivalentTo(new List<ProcessedCustomFormatData>
{
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<ProcessedCustomFormatData>
{
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<string>
var guideData = new List<CustomFormatData>
{
@"{'name': 'name1', 'trash_id': 'id1'}",
@"{'name': 'name2', 'trash_id': 'id2'}"
NewCf.Data("name1", "id1"),
NewCf.Data("name2", "id2")
};
var testConfig = new List<CustomFormatConfig>
@ -325,7 +300,7 @@ public class CustomFormatStepTest
processor.ProcessedCustomFormats.Should()
.BeEquivalentTo(new List<ProcessedCustomFormatData>
{
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<string>
var guideData = new List<CustomFormatData>
{
@"{'name': 'name1', 'trash_id': 'id1', 'trash_score': 100}"
NewCf.Data("name1", "id1", 100)
};
var testConfig = new List<CustomFormatConfig>
@ -376,10 +351,7 @@ public class CustomFormatStepTest
processor.ProcessedCustomFormats.Should()
.BeEquivalentTo(new List<ProcessedCustomFormatData>
{
new("name1", "id1", JObject.FromObject(new {name = "name1"}))
{
Score = 100
}
NewCf.Processed("name1", "id1", 100)
},
op => op.Using(new JsonEquivalencyStep()));
}

@ -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<ProcessedCustomFormatData>
{
new("name1", "id1", new JObject()) {Score = null}
NewCf.Processed("name1", "id1")
},
QualityProfiles = new List<QualityProfileConfig>
{
@ -46,7 +45,7 @@ public class QualityProfileStepTest
{
CustomFormats = new List<ProcessedCustomFormatData>
{
new("", "id1", new JObject()) {Score = 100}
NewCf.Processed("", "id1", 100)
},
QualityProfiles = new List<QualityProfileConfig>
{
@ -75,7 +74,7 @@ public class QualityProfileStepTest
{
CustomFormats = new List<ProcessedCustomFormatData>
{
new("", "id1", new JObject()) {Score = 100}
NewCf.Processed("", "id1", 100)
},
QualityProfiles = new List<QualityProfileConfig>
{
@ -110,7 +109,7 @@ public class QualityProfileStepTest
{
CustomFormats = new List<ProcessedCustomFormatData>
{
new("name1", "id1", new JObject()) {Score = 0}
NewCf.Processed("name1", "id1", 0)
},
QualityProfiles = new List<QualityProfileConfig>
{

@ -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]

@ -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<ProcessedCustomFormatData>
{
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<List<JObject>>(radarrCfData);
var guideCfs = new List<ProcessedCustomFormatData>
{
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<ProcessedCustomFormatData>
{
new("updated", "", guideCfData) {CacheEntry = new TrashIdMapping("", "") {CustomFormatId = 1}}
NewCf.Processed("updated", "", guideCfData, new TrashIdMapping("", "") {CustomFormatId = 1})
};
var radarrCfs = JsonConvert.DeserializeObject<List<JObject>>(radarrCfData);
@ -408,8 +406,8 @@ public class JsonTransactionStepTest
var radarrCfs = JsonConvert.DeserializeObject<List<JObject>>(radarrCfData);
var guideCfs = new List<ProcessedCustomFormatData>
{
new("updated", "", guideCfData![0]),
new("no_change", "", guideCfData[1])
NewCf.Processed("updated", "", guideCfData![0]),
NewCf.Processed("no_change", "", guideCfData[1])
};
var processor = new JsonTransactionStep();

@ -47,11 +47,8 @@ public class QualityProfileApiPersistenceStepTest
var cfScores = new Dictionary<string, QualityProfileCustomFormatScoreMapping>
{
{
"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))
}
};

@ -1,6 +1,8 @@
using TrashLib.Radarr.CustomFormat.Models;
namespace TrashLib.Radarr.CustomFormat.Guide;
public interface IRadarrGuideService
{
IEnumerable<string> GetCustomFormatJson();
IEnumerable<CustomFormatData> GetCustomFormatData();
}

@ -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<string> GetCustomFormatJson()
public IEnumerable<CustomFormatData> 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<CustomFormatData?> 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<CustomFormatData>();
});
}
public static CustomFormatData ParseCustomFormatData(string guideData)
{
var obj = JObject.Parse(guideData);
var name = obj.ValueOrThrow<string>("name");
var trashId = obj.ValueOrThrow<string>("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);
}
}

@ -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
);

@ -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)

@ -17,7 +17,7 @@ internal class GuideProcessor : IGuideProcessor
{
private readonly IRadarrGuideService _guideService;
private readonly Func<IGuideProcessorSteps> _stepsFactory;
private IList<string>? _guideCustomFormatJson;
private IList<CustomFormatData>? _guideCustomFormatJson;
private IGuideProcessorSteps _steps;
public GuideProcessor(IRadarrGuideService guideService, Func<IGuideProcessorSteps> 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.

@ -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<TrashIdMapping> DeletedCustomFormatsInCache => _deletedCustomFormatsInCache;
public IDictionary<string, List<ProcessedCustomFormatData>> DuplicatedCustomFormats => _duplicatedCustomFormats;
public void Process(IEnumerable<string> customFormatGuideData,
IReadOnlyCollection<CustomFormatConfig> config, CustomFormatCache? cache)
public void Process(
IList<CustomFormatData> customFormatGuideData,
IReadOnlyCollection<CustomFormatConfig> 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<string>("name");
var trashId = obj.ValueOrThrow<string>("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)
};
}

@ -11,6 +11,6 @@ public interface ICustomFormatStep
IReadOnlyCollection<(string, string)> CustomFormatsWithOutdatedNames { get; }
IDictionary<string, List<ProcessedCustomFormatData>> DuplicatedCustomFormats { get; }
void Process(IEnumerable<string> customFormatGuideData,
void Process(IList<CustomFormatData> customFormatGuideData,
IReadOnlyCollection<CustomFormatConfig> config, CustomFormatCache? cache);
}

Loading…
Cancel
Save