feat(radarr): support new custom format guide structure

Custom formats are now a list of JSON files in the github repository.
Support for parsing the custom format markdown data has been removed.
recyclarr
Robert Dailey 3 years ago
parent d8397634de
commit df203e3b46

@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- Support the new custom format structure in the guide: JSON files are parsed directly now. Trash
Updater no longer parses the markdown file.
## [1.5.1] - 2021-05-26
### Changed

@ -3589,6 +3589,15 @@ resharper_xunit_xunit_test_with_console_output_highlighting = warning
indent_style = space
indent_size = 2
[*.{xml,xsd}]
indent_style = space
indent_size = 2
[*.json]
indent_style = space
indent_size = 2
ij_javascript_array_initializer_right_brace_on_new_line = false
[{*.overridetasks,*.targets,*.tasks}]
indent_style = space
indent_size = 2
@ -3601,7 +3610,7 @@ indent_size = 2
indent_style = space
indent_size = 2
[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml}]
indent_style = space
indent_size = 4
tab_width = 4

@ -0,0 +1,32 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Trash.Radarr.CustomFormat.Guide;
namespace Trash.Tests.Radarr.CustomFormat.Guide
{
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class GithubCustomFormatJsonRequesterTest
{
[Test]
public async Task Requesting_json_from_github_works()
{
var requester = new GithubCustomFormatJsonRequester();
var jsonList = (await requester.GetCustomFormatJson()).ToList();
Action act = () => JObject.Parse(jsonList.First());
// As of the time this test was written, there are around 58 custom format JSON files.
// This number can fluctuate over time, but I'm only interested in verifying we get a handful
// of files in the response.
jsonList.Should().HaveCountGreaterOrEqualTo(5);
act.Should().NotThrow();
}
}
}

@ -1,71 +0,0 @@

<sub><sub><sub>Score [480]</sub>
??? example "json"
```json
{
"trash_id": "4eb3c272d48db8ab43c2c85283b69744",
"name": "DTS-HD/DTS:X",
"includeCustomFormatWhenRenaming": false,
"specifications": [{
"name": "dts.?(hd|es|x(?!\\d))",
"implementation": "ReleaseTitleSpecification",
"negate": false,
"required": false,
"fields": {
"value": "dts.?(hd|es|x(?!\\d))"
}
}]
}
```
<sub><sup>[TOP](#index)</sup>
------
### Surround Sound
>If you prefer all kind of surround sounds
!!! warning
Don't use this Custom Format in combination with the `Audio Advanced` CF if you want to fine tune your audio formats or else it will add up the scores.
<sub><sub><sub>Score [500]</sub>
??? example "json"
```json
{
"trash_id": "43bb5f09c79641e7a22e48d440bd8868",
"name": "Surround Sound",
"includeCustomFormatWhenRenaming": false,
"specifications": [{
"name": "dts\\-?(hd|x)|truehd|atmos|dd(\\+|p)(5|7)",
"implementation": "ReleaseTitleSpecification",
"negate": false,
"required": false,
"fields": {
"value": "dts\\-?(hd|x)|truehd|atmos|dd(\\+|p)(5|7)"
}
}]
}
```
```json
{
"trash_id": "abc",
"name": "No Score"
}
```
```json
{
"trash_id": "xyz",
"name": "One that won't be in config"
}
```

@ -1,5 +1,6 @@
{
"trash_id": "43bb5f09c79641e7a22e48d440bd8868",
"trash_score": 500,
"name": "Surround Sound",
"includeCustomFormatWhenRenaming": false,
"specifications": [{

@ -1,5 +1,6 @@
{
"trash_id": "4eb3c272d48db8ab43c2c85283b69744",
"trash_score": 480,
"name": "DTS-HD/DTS:X",
"includeCustomFormatWhenRenaming": false,
"specifications": [{

@ -0,0 +1,4 @@
{
"trash_id": "abc",
"name": "No Score"
}

@ -0,0 +1,5 @@
{
"trash_id": "xyz",
"trash_score": -100,
"name": "One that won't be in config"
}

@ -51,16 +51,27 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors
[Test]
[SuppressMessage("Maintainability", "CA1506", Justification = "Designed to be a high-level integration test")]
public void Guide_processor_behaves_as_expected_with_normal_markdown()
public void Guide_processor_behaves_as_expected_with_normal_guide_data()
{
var ctx = new Context();
var guideProcessor =
new GuideProcessor(ctx.Logger, new CustomFormatGuideParser(ctx.Logger),
new GuideProcessor(ctx.Logger, new GithubCustomFormatJsonRequester(),
() => new TestGuideProcessorSteps());
// simulate guide data
using var testHttp = new HttpTest();
testHttp.RespondWith(ctx.Data.ReadData("CF_Markdown1.md"));
testHttp.RespondWithJson(new[]
{
new {name = "ImportableCustomFormat1.json", type = "file", download_url = "http://not_real/file.json"},
new {name = "ImportableCustomFormat2.json", type = "file", download_url = "http://not_real/file.json"},
new {name = "NoScore.json", type = "file", download_url = "http://not_real/file.json"},
new {name = "WontBeInConfig.json", type = "file", download_url = "http://not_real/file.json"}
});
testHttp.RespondWithJson(ctx.ReadJson("ImportableCustomFormat1.json"));
testHttp.RespondWithJson(ctx.ReadJson("ImportableCustomFormat2.json"));
testHttp.RespondWithJson(ctx.ReadJson("NoScore.json"));
testHttp.RespondWithJson(ctx.ReadJson("WontBeInConfig.json"));
// Simulate user config in YAML
var config = new List<CustomFormatConfig>
@ -102,24 +113,23 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors
new("No Score", "abc", JObject.FromObject(new {name = "No Score"}))
};
guideProcessor.ProcessedCustomFormats.Should().BeEquivalentTo(expectedProcessedCustomFormatData,
op => op.Using(new JsonEquivalencyStep()));
guideProcessor.ProcessedCustomFormats.Should()
.BeEquivalentTo(expectedProcessedCustomFormatData, op => op.Using(new JsonEquivalencyStep()));
guideProcessor.ConfigData.Should().BeEquivalentTo(new List<ProcessedConfigData>
{
new()
{
CustomFormats = expectedProcessedCustomFormatData,
QualityProfiles = config[0].QualityProfiles
},
new()
guideProcessor.ConfigData.Should()
.BeEquivalentTo(new List<ProcessedConfigData>
{
CustomFormats = expectedProcessedCustomFormatData.GetRange(2, 1),
QualityProfiles = config[1].QualityProfiles
}
}, op => op
.Using<JToken>(jctx => jctx.Subject.Should().BeEquivalentTo(jctx.Expectation))
.WhenTypeIs<JToken>());
new()
{
CustomFormats = expectedProcessedCustomFormatData,
QualityProfiles = config[0].QualityProfiles
},
new()
{
CustomFormats = expectedProcessedCustomFormatData.GetRange(2, 1),
QualityProfiles = config[1].QualityProfiles
}
}, op => op.Using(new JsonEquivalencyStep()));
guideProcessor.CustomFormatsWithoutScore.Should()
.Equal(new List<(string name, string trashId, string profileName)>

@ -6,7 +6,6 @@ using Newtonsoft.Json.Linq;
using NUnit.Framework;
using TestLibrary.FluentAssertions;
using Trash.Radarr;
using Trash.Radarr.CustomFormat.Guide;
using Trash.Radarr.CustomFormat.Models;
using Trash.Radarr.CustomFormat.Models.Cache;
using Trash.Radarr.CustomFormat.Processors.GuideSteps;
@ -19,34 +18,23 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
{
private class Context
{
public List<CustomFormatData> TestGuideData { get; } = new()
public List<string> TestGuideData { get; } = new()
{
new CustomFormatData
JsonConvert.SerializeObject(new
{
Score = 100,
Json = JsonConvert.SerializeObject(new
{
trash_id = "id1",
name = "name1"
}, Formatting.Indented)
},
new CustomFormatData
trash_id = "id1",
name = "name1"
}, Formatting.Indented),
JsonConvert.SerializeObject(new
{
Score = 200,
Json = JsonConvert.SerializeObject(new
{
trash_id = "id2",
name = "name2"
}, Formatting.Indented)
},
new CustomFormatData
trash_id = "id2",
name = "name2"
}, Formatting.Indented),
JsonConvert.SerializeObject(new
{
Json = JsonConvert.SerializeObject(new
{
trash_id = "id3",
name = "name3"
}, Formatting.Indented)
}
trash_id = "id3",
name = "name3"
}, Formatting.Indented)
};
}
@ -69,17 +57,13 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
}
};
var testGuideData = new List<CustomFormatData>
var testGuideData = new List<string>
{
new()
JsonConvert.SerializeObject(new
{
Score = 100,
Json = JsonConvert.SerializeObject(new
{
trash_id = "id1",
name = variableCfName
}, Formatting.Indented)
}
trash_id = "id1",
name = variableCfName
}, Formatting.Indented)
};
var processor = new CustomFormatStep();
@ -92,7 +76,6 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
{
new(variableCfName, "id1", JObject.FromObject(new {name = variableCfName}))
{
Score = 100,
CacheEntry = testCache.TrashIdMappings[0]
}
},
@ -102,12 +85,9 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
[Test]
public void Cache_entry_is_not_set_when_id_is_different()
{
var guideData = new List<CustomFormatData>
var guideData = new List<string>
{
new()
{
Json = @"{'name': 'name1', 'trash_id': 'id1'}"
}
@"{'name': 'name1', 'trash_id': 'id1'}"
};
var testConfig = new List<CustomFormatConfig>
@ -157,14 +137,8 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
processor.DeletedCustomFormatsInCache.Should().BeEmpty();
processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List<ProcessedCustomFormatData>
{
new("name1", "id1", JObject.FromObject(new {name = "name1"}))
{
Score = 100
},
new("name3", "id3", JObject.FromObject(new {name = "name3"}))
{
Score = null
}
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()));
}
@ -187,8 +161,8 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
processor.DeletedCustomFormatsInCache.Should().BeEmpty();
processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List<ProcessedCustomFormatData>
{
new("name1", "id1", JObject.FromObject(new {name = "name1"})) {Score = 100},
new("name2", "id2", JObject.FromObject(new {name = "name2"})) {Score = 200},
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}
},
op => op.Using(new JsonEquivalencyStep()));
@ -197,12 +171,9 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
[Test]
public void Custom_format_is_deleted_if_in_config_and_cache_but_not_in_guide()
{
var guideData = new List<CustomFormatData>
var guideData = new List<string>
{
new()
{
Json = @"{'name': 'name1', 'trash_id': 'id1'}"
}
@"{'name': 'name1', 'trash_id': 'id1'}"
};
var testConfig = new List<CustomFormatConfig>
@ -237,9 +208,9 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
TrashIdMappings = new List<TrashIdMapping> {new("id1", "3D", 9)}
};
var guideCfs = new List<CustomFormatData>
var guideCfs = new List<string>
{
new() {Json = "{'name': '3D', 'trash_id': 'id1'}"}
"{'name': '3D', 'trash_id': 'id1'}"
};
var processor = new CustomFormatStep();
@ -254,12 +225,9 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
[Test]
public void Custom_format_name_in_cache_is_updated_if_renamed_in_guide_and_config()
{
var guideData = new List<CustomFormatData>
var guideData = new List<string>
{
new()
{
Json = @"{'name': 'name2', 'trash_id': 'id1'}"
}
@"{'name': 'name2', 'trash_id': 'id1'}"
};
var testConfig = new List<CustomFormatConfig>
@ -286,10 +254,10 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
[Test]
public void Duplicates_are_recorded_and_removed_from_processed_custom_formats_list()
{
var guideData = new List<CustomFormatData>
var guideData = new List<string>
{
new() {Json = @"{'name': 'name1', 'trash_id': 'id1'}"},
new() {Json = @"{'name': 'name1', 'trash_id': 'id2'}"}
@"{'name': 'name1', 'trash_id': 'id1'}",
@"{'name': 'name1', 'trash_id': 'id2'}"
};
var testConfig = new List<CustomFormatConfig>
@ -329,7 +297,7 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
processor.DeletedCustomFormatsInCache.Should().BeEmpty();
processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List<ProcessedCustomFormatData>
{
new("name1", "id1", JObject.FromObject(new {name = "name1"})) {Score = 100}
new("name1", "id1", JObject.FromObject(new {name = "name1"}))
},
op => op.Using(new JsonEquivalencyStep()));
}
@ -337,10 +305,10 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
[Test]
public void Match_custom_format_using_trash_id()
{
var guideData = new List<CustomFormatData>
var guideData = new List<string>
{
new() {Json = @"{'name': 'name1', 'trash_id': 'id1'}"},
new() {Json = @"{'name': 'name2', 'trash_id': 'id2'}"}
@"{'name': 'name1', 'trash_id': 'id1'}",
@"{'name': 'name2', 'trash_id': 'id2'}"
};
var testConfig = new List<CustomFormatConfig>
@ -382,9 +350,9 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors.GuideSteps
[Test]
public void Score_from_json_takes_precedence_over_score_from_guide()
{
var guideData = new List<CustomFormatData>
var guideData = new List<string>
{
new() {Json = @"{'name': 'name1', 'trash_id': 'id1', 'trash_score': 100}"}
@"{'name': 'name1', 'trash_id': 'id1', 'trash_score': 100}"
};
var testConfig = new List<CustomFormatConfig>

@ -7,8 +7,4 @@
<ProjectReference Include="..\TestLibrary\TestLibrary.csproj" />
<ProjectReference Include="..\Trash\Trash.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="Radarr\CustomFormat\Processors\Data\CF_Markdown1.md" />
</ItemGroup>
</Project>

@ -74,7 +74,7 @@ namespace Trash
// Custom Format Support
builder.RegisterType<CustomFormatUpdater>().As<ICustomFormatUpdater>();
builder.RegisterType<CustomFormatGuideParser>().As<ICustomFormatGuideParser>();
builder.RegisterType<GithubCustomFormatJsonRequester>().As<IRadarrGuideService>();
builder.RegisterType<CachePersister>().As<ICachePersister>();
// Guide Processor

@ -1,8 +0,0 @@
namespace Trash.Radarr.CustomFormat.Guide
{
public class CustomFormatData
{
public int? Score { get; set; } = null;
public string Json { get; set; } = "";
}
}

@ -1,149 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Flurl.Http;
using Serilog;
using Trash.Extensions;
namespace Trash.Radarr.CustomFormat.Guide
{
public class CustomFormatGuideParser : ICustomFormatGuideParser
{
private readonly Regex _regexFence = BuildRegex(@"(\s*)```(json)?");
private readonly Regex _regexPotentialScore = BuildRegex(@"\[(-?[\d]+)\]");
private readonly Regex _regexScore = BuildRegex(@"score.*?\[(-?[\d]+)\]");
public CustomFormatGuideParser(ILogger logger)
{
Log = logger;
}
private ILogger Log { get; }
public async Task<string> GetMarkdownData()
{
return await
"https://raw.githubusercontent.com/TRaSH-/Guides/master/docs/Radarr/V3/Radarr-collection-of-custom-formats.md"
.GetStringAsync();
}
public IList<CustomFormatData> ParseMarkdown(string markdown)
{
var state = new ParserState();
var reader = new StringReader(markdown);
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
state.LineNumber++;
if (string.IsNullOrEmpty(line))
{
continue;
}
// Always check if we're starting a fenced code block. Whether we are inside one or not greatly affects
// the logic we use.
if (_regexFence.Match(line, out Match match))
{
ProcessCodeBlockBoundary(match.Groups, state);
continue;
}
if (state.CodeBlockIndentation != null)
{
InsideFence_ParseMarkdown(line, state);
}
else
{
OutsideFence_ParseMarkdown(line, state);
}
}
return state.Results;
}
private static Regex BuildRegex(string regex)
{
return new(regex, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
private void OutsideFence_ParseMarkdown(string line, ParserState state)
{
// ReSharper disable once InlineOutVariableDeclaration
Match match;
if (_regexScore.Match(line, out match))
{
state.Score = int.Parse(match.Groups[1].Value);
}
else if (_regexPotentialScore.Match(line, out match))
{
Log.Warning("Found a potential score on line #{Line} that will be ignored because the " +
"word 'score' is missing (This is probably a bug in the guide itself): {ScoreMatch}",
state.LineNumber, match.Groups[0].Value);
}
}
private void ProcessCodeBlockBoundary(GroupCollection groups, ParserState state)
{
if (groups[2].Value == "json")
{
state.CodeBlockIndentation = groups[1].Value;
}
else
{
// Record previously captured JSON data since we're leaving the code block
var json = state.JsonStream.ToString();
if (!string.IsNullOrEmpty(json))
{
state.Results.Add(new CustomFormatData {Json = json, Score = state.Score});
}
state.ResetParserState();
}
}
private static void InsideFence_ParseMarkdown(string line, ParserState state)
{
state.JsonStream.WriteLine(line[state.CodeBlockIndentation!.Length..]);
}
// private void OutsideFence_ParseMarkdown(string line, RadarrParserState state)
// {
// // ReSharper disable once InlineOutVariableDeclaration
// Match match;
//
// // Header Processing. Never do any additional processing to headers, so return after processing it
// if (_regexHeader.Match(line, out match))
// {
// OutsideFence_ParseHeader(state, match);
// // return here if we add more logic below
// }
// }
// private void OutsideFence_ParseHeader(RadarrParserState state, Match match)
// {
// var headerDepth = match.Groups[1].Length;
// var headerText = match.Groups[2].Value;
//
// var stack = state.HeaderStack;
// while (stack.Count > 0 && stack.Peek().Item1 >= headerDepth)
// {
// stack.Pop();
// }
//
// if (headerDepth == 0)
// {
// return;
// }
//
// if (state.HeaderStack.TryPeek(out var header))
// {
// headerText = $"{header.Item2}|{headerText}";
// }
//
// Log.Debug("> Process Header: {HeaderPath}", headerText);
// state.HeaderStack.Push((headerDepth, headerText));
// }
}
}

@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Flurl.Http;
using Flurl.Http.Configuration;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Trash.Radarr.CustomFormat.Guide
{
public class GithubCustomFormatJsonRequester : IRadarrGuideService
{
private readonly ISerializer _flurlSerializer;
public GithubCustomFormatJsonRequester()
{
// In addition to setting the naming strategy, this also serves as a mechanism to avoid inheriting the
// global Flurl serializer setting: MissingMemberHandling. We do not want missing members to error out
// since we're only deserializing a subset of the github response object.
_flurlSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
});
}
public async Task<IEnumerable<string>> GetCustomFormatJson()
{
var response = await "https://api.github.com/repos/TRaSH-/Guides/contents/docs/json/radarr"
.WithHeader("User-Agent", "Trash Updater")
.ConfigureRequest(settings => settings.JsonSerializer = _flurlSerializer)
.GetJsonAsync<List<RepoContentEntry>>();
var tasks = response
.Where(o => o.Type == "file" && o.Name.EndsWith(".json"))
.Select(o => DownloadJsonContents(o.DownloadUrl));
return await Task.WhenAll(tasks);
}
private async Task<string> DownloadJsonContents(string jsonUrl)
{
return await jsonUrl.GetStringAsync();
}
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
private record RepoContentEntry
{
public string Name { get; init; } = default!;
public string Type { get; init; } = default!;
public string DownloadUrl { get; init; } = default!;
}
}
}

@ -1,11 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Trash.Radarr.CustomFormat.Guide
{
public interface ICustomFormatGuideParser
{
Task<string> GetMarkdownData();
IList<CustomFormatData> ParseMarkdown(string markdown);
}
}

@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Trash.Radarr.CustomFormat.Guide
{
public interface IRadarrGuideService
{
Task<IEnumerable<string>> GetCustomFormatJson();
}
}

@ -1,26 +0,0 @@
using System.Collections.Generic;
using System.IO;
namespace Trash.Radarr.CustomFormat.Guide
{
public class ParserState
{
public ParserState()
{
ResetParserState();
}
public int? Score { get; set; }
public string? CodeBlockIndentation { get; set; }
public int LineNumber { get; set; }
public List<CustomFormatData> Results { get; } = new();
public StringWriter JsonStream { get; } = new();
public void ResetParserState()
{
CodeBlockIndentation = null;
JsonStream.GetStringBuilder().Clear();
Score = null;
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
using Trash.Radarr.CustomFormat.Guide;
@ -18,15 +19,14 @@ namespace Trash.Radarr.CustomFormat.Processors
internal class GuideProcessor : IGuideProcessor
{
private readonly ICustomFormatGuideParser _guideParser;
private readonly IRadarrGuideService _guideService;
private readonly Func<IGuideProcessorSteps> _stepsFactory;
private IList<CustomFormatData>? _guideData;
private IList<string>? _guideCustomFormatJson;
private IGuideProcessorSteps _steps;
public GuideProcessor(ILogger log, ICustomFormatGuideParser guideParser,
Func<IGuideProcessorSteps> stepsFactory)
public GuideProcessor(ILogger log, IRadarrGuideService guideService, Func<IGuideProcessorSteps> stepsFactory)
{
_guideParser = guideParser;
_guideService = guideService;
_stepsFactory = stepsFactory;
Log = log;
_steps = stepsFactory();
@ -60,16 +60,15 @@ namespace Trash.Radarr.CustomFormat.Processors
public async Task BuildGuideData(IReadOnlyList<CustomFormatConfig> config, CustomFormatCache? cache)
{
if (_guideData == null)
if (_guideCustomFormatJson == null)
{
Log.Debug("Requesting and parsing guide markdown");
var markdownData = await _guideParser.GetMarkdownData();
_guideData = _guideParser.ParseMarkdown(markdownData);
_guideCustomFormatJson = (await _guideService.GetCustomFormatJson()).ToList();
}
// Step 1: Process and filter the custom formats from the guide.
// Custom formats in the guide not mentioned in the config are filtered out.
_steps.CustomFormat.Process(_guideData, config, cache);
_steps.CustomFormat.Process(_guideCustomFormatJson, config, cache);
// todo: Process cache entries that do not exist in the guide. Those should be deleted
// This might get taken care of when we rebuild the cache based on what is actually updated when

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Trash.Extensions;
using Trash.Radarr.CustomFormat.Guide;
using Trash.Radarr.CustomFormat.Models;
using Trash.Radarr.CustomFormat.Models.Cache;
@ -18,7 +17,7 @@ namespace Trash.Radarr.CustomFormat.Processors.GuideSteps
public Dictionary<string, List<ProcessedCustomFormatData>> DuplicatedCustomFormats { get; private set; } =
new();
public void Process(IEnumerable<CustomFormatData> customFormatGuideData,
public void Process(IEnumerable<string> customFormatGuideData,
IReadOnlyCollection<CustomFormatConfig> config, CustomFormatCache? cache)
{
var processedCfs = customFormatGuideData
@ -93,23 +92,18 @@ namespace Trash.Radarr.CustomFormat.Processors.GuideSteps
ProcessedCustomFormats.RemoveAll(cf => DuplicatedCustomFormats.ContainsKey(cf.Name));
}
private static ProcessedCustomFormatData ProcessCustomFormatData(CustomFormatData guideData,
CustomFormatCache? cache)
private static ProcessedCustomFormatData ProcessCustomFormatData(string guideData, CustomFormatCache? cache)
{
JObject obj = JObject.Parse(guideData.Json);
JObject obj = JObject.Parse(guideData);
var name = (string) obj["name"];
var trashId = (string) obj["trash_id"];
int? finalScore;
int? finalScore = null;
if (obj.TryGetValue("trash_score", out var score))
{
finalScore = (int) score;
obj.Property("trash_score").Remove();
}
else
{
finalScore = guideData.Score;
}
// 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

@ -1,5 +1,4 @@
using System.Collections.Generic;
using Trash.Radarr.CustomFormat.Guide;
using Trash.Radarr.CustomFormat.Models;
using Trash.Radarr.CustomFormat.Models.Cache;
@ -12,7 +11,7 @@ namespace Trash.Radarr.CustomFormat.Processors.GuideSteps
List<(string, string)> CustomFormatsWithOutdatedNames { get; }
Dictionary<string, List<ProcessedCustomFormatData>> DuplicatedCustomFormats { get; }
void Process(IEnumerable<CustomFormatData> customFormatGuideData,
void Process(IEnumerable<string> customFormatGuideData,
IReadOnlyCollection<CustomFormatConfig> config, CustomFormatCache? cache);
}
}

Loading…
Cancel
Save