From 085d641e7e6c5e2887aaf74904642592ddbaf272 Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Thu, 24 Jun 2021 17:18:52 -0500 Subject: [PATCH] fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Add initial Blazor Server project --- .../Code/Database/DatabaseContext.cs | 10 ++ .../Code/Radarr/CustomFormatRepository.cs | 59 -------- src/Recyclarr/Code/Radarr/GuideProcessor.cs | 103 +++++++++++++ src/Recyclarr/Code/Radarr/IGuideProcessor.cs | 19 +++ src/Recyclarr/CompositionRoot.cs | 7 +- .../CustomFormatAccessLayout.razor | 2 +- .../CustomFormatAccessLayout.razor.cs | 8 +- .../CustomFormats/CustomFormatsPage.razor.cs | 12 +- .../Pages/Radarr/QualityProfilesPage.razor | 23 +-- .../Pages/Radarr/Servers/ServersPage.razor.cs | 10 +- src/Recyclarr/Recyclarr.csproj | 3 + src/Trash/Command/RadarrCommand.cs | 4 +- .../Processors/PersistenceProcessorTest.cs | 8 +- .../Radarr/RadarrConfigurationTest.cs | 12 +- ...RadarrConfiguration.cs => RadarrConfig.cs} | 29 ++-- .../Config/RadarrConfigurationValidator.cs | 2 +- .../{ => Cache}/CachePersister.cs | 29 ++-- .../Cache/CachePersisterFactory.cs | 25 ++++ .../{ => Cache}/ICachePersister.cs | 2 +- .../Cache/ICachePersisterFactory.cs | 9 ++ .../CustomFormat/CustomFormatUpdater.cs | 5 +- .../CustomFormat/ICustomFormatUpdater.cs | 2 +- .../Models/Cache/CustomFormatCache.cs | 6 +- .../Models/ProcessedCustomFormatData.cs | 5 +- .../CustomFormat/Processors/GuideProcessor.cs | 94 ------------ .../Processors/GuideSteps/ConfigStep.cs | 68 --------- .../GuideSteps/CustomFormatProcessor.cs | 76 ++++++++++ .../Processors/GuideSteps/CustomFormatStep.cs | 135 ------------------ .../Processors/GuideSteps/IConfigStep.cs | 15 -- ...ormatStep.cs => ICustomFormatProcessor.cs} | 8 +- .../Processors/IGuideProcessor.cs | 23 --- .../Processors/IPersistenceProcessor.cs | 2 +- .../Processors/PersistenceProcessor.cs | 2 +- .../IRadarrQualityDefinitionUpdater.cs | 2 +- .../RadarrQualityDefinitionUpdater.cs | 2 +- src/TrashLib/Radarr/RadarrAutofacModule.cs | 42 ++---- src/recyclarr.sqlite | 0 37 files changed, 346 insertions(+), 517 deletions(-) create mode 100644 src/Recyclarr/Code/Database/DatabaseContext.cs delete mode 100644 src/Recyclarr/Code/Radarr/CustomFormatRepository.cs create mode 100644 src/Recyclarr/Code/Radarr/GuideProcessor.cs create mode 100644 src/Recyclarr/Code/Radarr/IGuideProcessor.cs rename src/TrashLib/Radarr/Config/{RadarrConfiguration.cs => RadarrConfig.cs} (54%) rename src/TrashLib/Radarr/CustomFormat/{ => Cache}/CachePersister.cs (68%) create mode 100644 src/TrashLib/Radarr/CustomFormat/Cache/CachePersisterFactory.cs rename src/TrashLib/Radarr/CustomFormat/{ => Cache}/ICachePersister.cs (88%) create mode 100644 src/TrashLib/Radarr/CustomFormat/Cache/ICachePersisterFactory.cs delete mode 100644 src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs delete mode 100644 src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ConfigStep.cs create mode 100644 src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatProcessor.cs delete mode 100644 src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs delete mode 100644 src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/IConfigStep.cs rename src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/{ICustomFormatStep.cs => ICustomFormatProcessor.cs} (61%) delete mode 100644 src/TrashLib/Radarr/CustomFormat/Processors/IGuideProcessor.cs create mode 100644 src/recyclarr.sqlite diff --git a/src/Recyclarr/Code/Database/DatabaseContext.cs b/src/Recyclarr/Code/Database/DatabaseContext.cs new file mode 100644 index 00000000..7ac6ac49 --- /dev/null +++ b/src/Recyclarr/Code/Database/DatabaseContext.cs @@ -0,0 +1,10 @@ +using Microsoft.EntityFrameworkCore; +using TrashLib.Radarr.Config; + +namespace Recyclarr.Code.Database +{ + public class DatabaseContext : DbContext + { + public DbSet RadarrConfigs { get; set; } + } +} diff --git a/src/Recyclarr/Code/Radarr/CustomFormatRepository.cs b/src/Recyclarr/Code/Radarr/CustomFormatRepository.cs deleted file mode 100644 index 0564f572..00000000 --- a/src/Recyclarr/Code/Radarr/CustomFormatRepository.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using TrashLib.Radarr.CustomFormat.Guide; - -namespace Recyclarr.Code.Radarr -{ - public class CustomFormatIdentifier - { - public CustomFormatIdentifier(string name, string trashId) - { - Name = name; - TrashId = trashId; - } - - public string Name { get; } - public string TrashId { get; } - } - - public class CustomFormatRepository - { - private readonly IRadarrGuideService _guideService; - private List? _customFormatIdentifiers; - - public CustomFormatRepository(IRadarrGuideService guideService) - { - _guideService = guideService; - } - - public IList Identifiers => - _customFormatIdentifiers ?? throw new NullReferenceException( - "CustomFormatRepository.BuildRepository() must be called before requesting a list of CF identifiers"); - - public bool IsLoaded => _customFormatIdentifiers != null; - - public async Task ForceRebuildRepository() - { - _customFormatIdentifiers = null; - await BuildRepository(); - } - - public async Task BuildRepository() - { - if (_customFormatIdentifiers != null) - { - return false; - } - - _customFormatIdentifiers = (await _guideService.GetCustomFormatJson()) - .Select(JObject.Parse) - .Select(json => new CustomFormatIdentifier((string) json["name"], (string) json["trash_id"])) - .ToList(); - - return true; - } - } -} diff --git a/src/Recyclarr/Code/Radarr/GuideProcessor.cs b/src/Recyclarr/Code/Radarr/GuideProcessor.cs new file mode 100644 index 00000000..829bbc06 --- /dev/null +++ b/src/Recyclarr/Code/Radarr/GuideProcessor.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Serilog; +using TrashLib.Radarr.Config; +using TrashLib.Radarr.CustomFormat.Api; +using TrashLib.Radarr.CustomFormat.Cache; +using TrashLib.Radarr.CustomFormat.Guide; +using TrashLib.Radarr.CustomFormat.Models; +using TrashLib.Radarr.CustomFormat.Models.Cache; +using TrashLib.Radarr.CustomFormat.Processors.GuideSteps; +using TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps; + +namespace Recyclarr.Code.Radarr +{ + internal class GuideProcessor : IGuideProcessor + { + private readonly ICachePersisterFactory _cachePersisterFactory; + private readonly ICustomFormatApiPersistenceStep _customFormatCustomFormatApiPersister; + private readonly ICustomFormatProcessor _customFormatProcessor; + private readonly Func _customFormatServiceFactory; + private readonly IRadarrGuideService _guideService; + private readonly IJsonTransactionStep _jsonTransactionStep; + private IList? _guideCustomFormatJson; + + public GuideProcessor( + ILogger log, + IRadarrGuideService guideService, + ICustomFormatProcessor customFormatProcessor, + ICachePersisterFactory cachePersisterFactory, + Func customFormatServiceFactory, + IJsonTransactionStep jsonTransactionStep, + ICustomFormatApiPersistenceStep customFormatCustomFormatApiPersister) + { + Log = log; + _guideService = guideService; + _customFormatProcessor = customFormatProcessor; + _cachePersisterFactory = cachePersisterFactory; + _customFormatServiceFactory = customFormatServiceFactory; + _jsonTransactionStep = jsonTransactionStep; + _customFormatCustomFormatApiPersister = customFormatCustomFormatApiPersister; + } + + private ILogger Log { get; } + + public IReadOnlyCollection ProcessedCustomFormats + => _customFormatProcessor.ProcessedCustomFormats; + + public IReadOnlyCollection DeletedCustomFormatsInCache + => _customFormatProcessor.DeletedCustomFormatsInCache; + + public List<(string, string)> CustomFormatsWithOutdatedNames + => _customFormatProcessor.CustomFormatsWithOutdatedNames; + + public bool IsLoaded => _guideCustomFormatJson is not null; + + public async Task ForceBuildGuideData(RadarrConfig config) + { + _guideCustomFormatJson = null; + await BuildGuideData(config); + } + + public async Task BuildGuideData(RadarrConfig config) + { + var cache = _cachePersisterFactory.Create(config); + cache.Load(); + + if (_guideCustomFormatJson == null) + { + Log.Debug("Requesting and parsing guide markdown"); + _guideCustomFormatJson = (await _guideService.GetCustomFormatJson()).ToList(); + } + + // Process and filter the custom formats from the guide. + // Custom formats in the guide not mentioned in the config are filtered out. + _customFormatProcessor.Process(_guideCustomFormatJson, config, cache.CfCache); + cache.Save(); + } + + public async Task SaveToRadarr(RadarrConfig config) + { + var customFormatService = _customFormatServiceFactory(config.BuildUrl()); + var radarrCfs = await customFormatService.GetCustomFormats(); + + // Match CFs between the guide & Radarr and merge the data. The goal is to retain as much of the + // original data from Radarr as possible. There are many properties in the response JSON that we don't + // directly care about. We keep those and just update the ones we do care about. + _jsonTransactionStep.Process(ProcessedCustomFormats, radarrCfs); + + // Step 1.1: Optionally record deletions of custom formats in cache but not in the guide + if (config.DeleteOldCustomFormats) + { + _jsonTransactionStep.RecordDeletions(DeletedCustomFormatsInCache, radarrCfs); + } + + // Step 2: For each merged CF, persist it to Radarr via its API. This will involve a combination of updates + // to existing CFs and creation of brand new ones, depending on what's already available in Radarr. + await _customFormatCustomFormatApiPersister.Process( + customFormatService, _jsonTransactionStep.Transactions); + } + } +} diff --git a/src/Recyclarr/Code/Radarr/IGuideProcessor.cs b/src/Recyclarr/Code/Radarr/IGuideProcessor.cs new file mode 100644 index 00000000..1bf7853b --- /dev/null +++ b/src/Recyclarr/Code/Radarr/IGuideProcessor.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TrashLib.Radarr.Config; +using TrashLib.Radarr.CustomFormat.Models; +using TrashLib.Radarr.CustomFormat.Models.Cache; + +namespace Recyclarr.Code.Radarr +{ + public interface IGuideProcessor + { + IReadOnlyCollection ProcessedCustomFormats { get; } + IReadOnlyCollection DeletedCustomFormatsInCache { get; } + List<(string, string)> CustomFormatsWithOutdatedNames { get; } + bool IsLoaded { get; } + Task BuildGuideData(RadarrConfig config); + Task SaveToRadarr(RadarrConfig config); + Task ForceBuildGuideData(RadarrConfig config); + } +} diff --git a/src/Recyclarr/CompositionRoot.cs b/src/Recyclarr/CompositionRoot.cs index bcfdf295..424b9545 100644 --- a/src/Recyclarr/CompositionRoot.cs +++ b/src/Recyclarr/CompositionRoot.cs @@ -34,14 +34,15 @@ namespace Recyclarr { builder.RegisterModule(); + builder.RegisterType().As(); builder.RegisterType() .InstancePerLifetimeScope(); - builder.RegisterType>() - .As>() + builder.RegisterType>() + .As>() .WithParameter(new NamedParameter("filename", "radarr.json")); - builder.Register(c => c.Resolve>().Load()) + builder.Register(c => c.Resolve>().Load()) .InstancePerLifetimeScope(); } } diff --git a/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatAccessLayout.razor b/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatAccessLayout.razor index c3996adb..49bb76f5 100644 --- a/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatAccessLayout.razor +++ b/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatAccessLayout.razor @@ -17,7 +17,7 @@ Retry } - else if (!CfRepository.IsLoaded) + else if (!GuideProcessor.IsLoaded) { Loading custom formats... diff --git a/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatAccessLayout.razor.cs b/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatAccessLayout.razor.cs index 07f7b369..c889986c 100644 --- a/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatAccessLayout.razor.cs +++ b/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatAccessLayout.razor.cs @@ -12,9 +12,9 @@ namespace Recyclarr.Pages.Radarr.CustomFormats public Action? OnReload { get; set; } [Inject] - public CustomFormatRepository CfRepository { get; set; } = default!; + public IGuideProcessor GuideProcessor { get; set; } = default!; - public bool IsLoaded => CfRepository.IsLoaded; + public bool IsLoaded => GuideProcessor.IsLoaded; protected override async Task OnInitializedAsync() { @@ -32,11 +32,11 @@ namespace Recyclarr.Pages.Radarr.CustomFormats if (force) { - await CfRepository.ForceRebuildRepository(); + await GuideProcessor.ForceBuildGuideData(); } else { - wasLoaded = await CfRepository.BuildRepository(); + wasLoaded = await GuideProcessor.BuildRepository(); } if (wasLoaded) diff --git a/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatsPage.razor.cs b/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatsPage.razor.cs index 2f0938fd..66450ee6 100644 --- a/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatsPage.razor.cs +++ b/src/Recyclarr/Pages/Radarr/CustomFormats/CustomFormatsPage.razor.cs @@ -17,7 +17,7 @@ namespace Recyclarr.Pages.Radarr.CustomFormats { private CustomFormatChooser? _cfChooser; private HashSet _currentSelection = new(); - private ServerSelector? _serverSelector; + private ServerSelector? _serverSelector; [CascadingParameter] public CustomFormatAccessLayout? CfAccessor { get; set; } @@ -26,10 +26,10 @@ namespace Recyclarr.Pages.Radarr.CustomFormats public IDialogService DialogService { get; set; } = default!; [Inject] - public IConfigPersister ConfigPersister { get; set; } = default!; + public IConfigPersister ConfigPersister { get; set; } = default!; [Inject] - public ICollection Configs { get; set; } = default!; + public ICollection Configs { get; set; } = default!; private bool? SelectAllCheckbox { get; set; } = false; private List ChosenCustomFormatIds => _currentSelection.Select(cf => cf.Item.TrashIds.First()).ToList(); @@ -37,7 +37,7 @@ namespace Recyclarr.Pages.Radarr.CustomFormats private bool IsAddSelectedDisabled => IsRefreshDisabled || _cfChooser!.SelectedCount == 0; private IList CustomFormatIds - => CfAccessor?.CfRepository.Identifiers ?? new List(); + => CfAccessor?.GuideProcessor.ProcessedCustomFormats ?? new List(); public void Dispose() { @@ -63,7 +63,7 @@ namespace Recyclarr.Pages.Radarr.CustomFormats StateHasChanged(); } - private void UpdateSelectedCustomFormats(RadarrConfiguration? selection) + private void UpdateSelectedCustomFormats(RadarrConfig? selection) { if (selection == null) { @@ -73,7 +73,7 @@ namespace Recyclarr.Pages.Radarr.CustomFormats _currentSelection = selection.CustomFormats .Select(cf => { - var exists = CfAccessor?.CfRepository.Identifiers.Any(id2 => id2.TrashId == cf.TrashIds.First()); + var exists = CfAccessor?.GuideProcessor.Identifiers.Any(id2 => id2.TrashId == cf.TrashIds.First()); return new SelectableCustomFormat(cf, exists ?? false); }) .ToHashSet(); diff --git a/src/Recyclarr/Pages/Radarr/QualityProfilesPage.razor b/src/Recyclarr/Pages/Radarr/QualityProfilesPage.razor index ec3ba8e6..a554f884 100644 --- a/src/Recyclarr/Pages/Radarr/QualityProfilesPage.razor +++ b/src/Recyclarr/Pages/Radarr/QualityProfilesPage.razor @@ -7,6 +7,7 @@ @using Flurl.Http @using TrashLib.Cache @using TrashLib.Radarr.CustomFormat +@using TrashLib.Radarr.CustomFormat.Cache
@@ -105,10 +106,10 @@ public Func ProfileServiceFactory { get; set; } = default!; [Inject] - public IConfigPersister ConfigPersister { get; set; } = default!; + public IConfigPersister ConfigPersister { get; set; } = default!; [Inject] - public ICollection Configs { get; set; } = default!; + public ICollection Configs { get; set; } = default!; protected override async Task OnInitializedAsync() { @@ -117,25 +118,25 @@ class ProfileSelectionPageManager { - private readonly Func _cachePersisterFactory; + private readonly ICachePersisterFactory _cachePersisterFactory; private readonly Func _customFormatServiceFactory; public ProfileSelectionPageManager( - Func cachePersisterFactory, + ICachePersisterFactory cachePersisterFactory, Func customFormatServiceFactory) { _cachePersisterFactory = cachePersisterFactory; _customFormatServiceFactory = customFormatServiceFactory; } - async Task RequestCustomFormatsAndUpdateCache(RadarrConfiguration config) + async Task RequestCustomFormatsAndUpdateCache(RadarrConfig config) { var cfService = _customFormatServiceFactory(config.BuildUrl()); var customFormats = await cfService.GetCustomFormats(); } } - private async Task OnSelectedInstanceChanged(RadarrConfiguration? activeConfig) + private async Task OnSelectedInstanceChanged(RadarrConfig? activeConfig) { try { @@ -151,9 +152,9 @@ // - Need to pair guide score with current profile score // - Exclude FormatItems that represent Custom Formats not selected by the user // - var qualityProfiles = await ProfileServiceFactory(activeConfig.BuildUrl()).GetQualityProfiles(); - qualityProfiles.Where(_activeConfig.CustomFormats) - _profiles.AddRange(); + // var qualityProfiles = await ProfileServiceFactory(activeConfig.BuildUrl()).GetQualityProfiles(); + // qualityProfiles.Where(_activeConfig.CustomFormats) + // _profiles.AddRange(); } SelectedProfile = _profiles.FirstOrDefault(); @@ -166,8 +167,8 @@ _loading = false; } - private RadarrConfiguration? _activeConfig; - private ServerSelector? _serverSelector; + private RadarrConfig? _activeConfig; + private ServerSelector? _serverSelector; private readonly List _profiles = new(); private Exception? _exception; private bool _loading; diff --git a/src/Recyclarr/Pages/Radarr/Servers/ServersPage.razor.cs b/src/Recyclarr/Pages/Radarr/Servers/ServersPage.razor.cs index 7778d98a..ddee772c 100644 --- a/src/Recyclarr/Pages/Radarr/Servers/ServersPage.razor.cs +++ b/src/Recyclarr/Pages/Radarr/Servers/ServersPage.razor.cs @@ -15,10 +15,10 @@ namespace Recyclarr.Pages.Radarr.Servers public IDialogService DialogService { get; set; } = default!; [Inject] - public IConfigPersister ConfigPersister { get; set; } = default!; + public IConfigPersister ConfigPersister { get; set; } = default!; [Inject] - public ICollection Configs { get; set; } = default!; + public ICollection Configs { get; set; } = default!; private async Task ShowEditServerModal(string title, ServiceConfiguration instance) { @@ -47,7 +47,7 @@ namespace Recyclarr.Pages.Radarr.Servers private async Task OnAddServer() { - var item = new RadarrConfiguration(); + var item = new RadarrConfig(); if (await ShowEditServerModal("Add Server", item)) { Configs.Add(item); @@ -55,7 +55,7 @@ namespace Recyclarr.Pages.Radarr.Servers } } - private async Task OnEdit(RadarrConfiguration item) + private async Task OnEdit(RadarrConfig item) { await ShowEditServerModal("Edit Server", item); SaveServers(); @@ -66,7 +66,7 @@ namespace Recyclarr.Pages.Radarr.Servers ConfigPersister.Save(Configs); } - private async Task OnDelete(RadarrConfiguration item) + private async Task OnDelete(RadarrConfig item) { var shouldDelete = await DialogService.ShowMessageBox( "Warning", diff --git a/src/Recyclarr/Recyclarr.csproj b/src/Recyclarr/Recyclarr.csproj index bff0449c..bf4e4750 100644 --- a/src/Recyclarr/Recyclarr.csproj +++ b/src/Recyclarr/Recyclarr.csproj @@ -6,6 +6,9 @@ + + + diff --git a/src/Trash/Command/RadarrCommand.cs b/src/Trash/Command/RadarrCommand.cs index 3664c095..19be4d54 100644 --- a/src/Trash/Command/RadarrCommand.cs +++ b/src/Trash/Command/RadarrCommand.cs @@ -18,7 +18,7 @@ namespace Trash.Command [UsedImplicitly] public class RadarrCommand : ServiceCommand { - private readonly IConfigurationLoader _configLoader; + private readonly IConfigurationLoader _configLoader; private readonly Func _customFormatUpdaterFactory; private readonly Func _qualityUpdaterFactory; @@ -26,7 +26,7 @@ namespace Trash.Command ILogger logger, LoggingLevelSwitch loggingLevelSwitch, ILogJanitor logJanitor, - IConfigurationLoader configLoader, + IConfigurationLoader configLoader, Func qualityUpdaterFactory, Func customFormatUpdaterFactory) : base(logger, loggingLevelSwitch, logJanitor) diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs index 233d4e13..283c16a7 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs @@ -41,7 +41,7 @@ namespace TrashLib.Tests.Radarr.CustomFormat.Processors [Test] public void Custom_formats_are_deleted_if_deletion_option_is_enabled_in_config() { - var config = new RadarrConfiguration {DeleteOldCustomFormats = true}; + var config = new RadarrConfig {DeleteOldCustomFormats = true}; var ctx = new Context(); ctx.Processor.PersistCustomFormats(config, ctx.GuideCfs, ctx.DeletedCfsInCache, ctx.ProfileScores); @@ -52,7 +52,7 @@ namespace TrashLib.Tests.Radarr.CustomFormat.Processors [Test] public void Custom_formats_are_not_deleted_if_deletion_option_is_disabled_in_config() { - var config = new RadarrConfiguration {DeleteOldCustomFormats = false}; + var config = new RadarrConfig {DeleteOldCustomFormats = false}; var ctx = new Context(); ctx.Processor.PersistCustomFormats(config, ctx.GuideCfs, ctx.DeletedCfsInCache, ctx.ProfileScores); @@ -65,10 +65,10 @@ namespace TrashLib.Tests.Radarr.CustomFormat.Processors { var ctx = new Context(); - var config = new RadarrConfiguration {DeleteOldCustomFormats = false}; + var config = new RadarrConfig {DeleteOldCustomFormats = false}; ctx.Processor.PersistCustomFormats(config, ctx.GuideCfs, ctx.DeletedCfsInCache, ctx.ProfileScores); - config = new RadarrConfiguration {DeleteOldCustomFormats = true}; + config = new RadarrConfig {DeleteOldCustomFormats = true}; ctx.Processor.PersistCustomFormats(config, ctx.GuideCfs, ctx.DeletedCfsInCache, ctx.ProfileScores); ctx.Steps.JsonTransactionStep.Received(1) diff --git a/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs b/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs index ab2c49a5..ea4a72e7 100644 --- a/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs +++ b/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs @@ -36,7 +36,7 @@ namespace TrashLib.Tests.Radarr public void Custom_format_is_valid_with_one_of_either_names_or_trash_id(List namesList, List trashIdsList) { - var config = new RadarrConfiguration + var config = new RadarrConfig { ApiKey = "required value", BaseUrl = "required value", @@ -46,7 +46,7 @@ namespace TrashLib.Tests.Radarr } }; - var validator = _container.Resolve>(); + var validator = _container.Resolve>(); var result = validator.Validate(config); result.IsValid.Should().BeTrue(); @@ -57,8 +57,8 @@ namespace TrashLib.Tests.Radarr public void Validation_fails_for_all_missing_required_properties() { // default construct which should yield default values (invalid) for all required properties - var config = new RadarrConfiguration(); - var validator = _container.Resolve>(); + var config = new RadarrConfig(); + var validator = _container.Resolve>(); var result = validator.Validate(config); @@ -79,7 +79,7 @@ namespace TrashLib.Tests.Radarr [Test] public void Validation_succeeds_when_no_missing_required_properties() { - var config = new RadarrConfiguration + var config = new RadarrConfig { ApiKey = "required value", BaseUrl = "required value", @@ -100,7 +100,7 @@ namespace TrashLib.Tests.Radarr } }; - var validator = _container.Resolve>(); + var validator = _container.Resolve>(); var result = validator.Validate(config); result.IsValid.Should().BeTrue(); diff --git a/src/TrashLib/Radarr/Config/RadarrConfiguration.cs b/src/TrashLib/Radarr/Config/RadarrConfig.cs similarity index 54% rename from src/TrashLib/Radarr/Config/RadarrConfiguration.cs rename to src/TrashLib/Radarr/Config/RadarrConfig.cs index 7bc365bd..6a95eebf 100644 --- a/src/TrashLib/Radarr/Config/RadarrConfiguration.cs +++ b/src/TrashLib/Radarr/Config/RadarrConfig.cs @@ -1,40 +1,43 @@ using System.Collections.Generic; -using JetBrains.Annotations; using TrashLib.Config; using TrashLib.Radarr.QualityDefinition; +// ReSharper disable ClassNeverInstantiated.Global + namespace TrashLib.Radarr.Config { - [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] - public class RadarrConfiguration : ServiceConfiguration + public class RadarrConfig : ServiceConfiguration { + public int Id { get; set; } public QualityDefinitionConfig? QualityDefinition { get; init; } - public List CustomFormats { get; set; } = new(); + public ICollection CustomFormats { get; set; }// = new(); + public ICollection QualityProfiles { get; init; }// = new(); public bool DeleteOldCustomFormats { get; init; } } - [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public class CustomFormatConfig { - public List Names { get; init; } = new(); - public List TrashIds { get; init; } = new(); - public List QualityProfiles { get; init; } = new(); + public string Name { get; init; } = ""; + public string TrashId { get; init; } = ""; } - [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public class QualityProfileConfig { - public string Name { get; init; } = ""; - public int? Score { get; init; } + public ICollection Scores { get; init; }// = new(); public bool ResetUnmatchedScores { get; init; } } - [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] + public class ScoreEntryConfig + { + public string TrashId { get; init; } = ""; + public int? Score { get; init; } + } + public class QualityDefinitionConfig { // -1 does not map to a valid enumerator. this is to force validation to fail if it is not set from YAML. // All of this craziness is to avoid making the enum type nullable. - public RadarrQualityDefinitionType Type { get; init; } = (RadarrQualityDefinitionType) (-1); + public RadarrQualityDefinitionType Type { get; set; } = (RadarrQualityDefinitionType) (-1); public decimal PreferredRatio { get; set; } = 1.0m; } diff --git a/src/TrashLib/Radarr/Config/RadarrConfigurationValidator.cs b/src/TrashLib/Radarr/Config/RadarrConfigurationValidator.cs index 21dcf3c8..824b95e1 100644 --- a/src/TrashLib/Radarr/Config/RadarrConfigurationValidator.cs +++ b/src/TrashLib/Radarr/Config/RadarrConfigurationValidator.cs @@ -5,7 +5,7 @@ using JetBrains.Annotations; namespace TrashLib.Radarr.Config { [UsedImplicitly] - internal class RadarrConfigurationValidator : AbstractValidator + internal class RadarrConfigurationValidator : AbstractValidator { public RadarrConfigurationValidator( IRadarrValidationMessages messages, diff --git a/src/TrashLib/Radarr/CustomFormat/CachePersister.cs b/src/TrashLib/Radarr/CustomFormat/Cache/CachePersister.cs similarity index 68% rename from src/TrashLib/Radarr/CustomFormat/CachePersister.cs rename to src/TrashLib/Radarr/CustomFormat/Cache/CachePersister.cs index 0850c9fc..25c03d46 100644 --- a/src/TrashLib/Radarr/CustomFormat/CachePersister.cs +++ b/src/TrashLib/Radarr/CustomFormat/Cache/CachePersister.cs @@ -5,7 +5,7 @@ using TrashLib.Cache; using TrashLib.Radarr.CustomFormat.Models; using TrashLib.Radarr.CustomFormat.Models.Cache; -namespace TrashLib.Radarr.CustomFormat +namespace TrashLib.Radarr.CustomFormat.Cache { internal class CachePersister : ICachePersister { @@ -20,28 +20,27 @@ namespace TrashLib.Radarr.CustomFormat } private ILogger Log { get; } + public CustomFormatCache? CfCache { get; private set; } public void Load() { CfCache = _cache.Load(_guidBuilder); - // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression - if (CfCache != null) + if (CfCache == null) { - Log.Debug("Loaded Cache"); - - // If the version is higher OR lower, we invalidate the cache. It means there's an - // incompatibility that we do not support. - if (CfCache.Version != CustomFormatCache.LatestVersion) - { - Log.Information("Cache version mismatch ({OldVersion} vs {LatestVersion}); ignoring cache data", - CfCache.Version, CustomFormatCache.LatestVersion); - CfCache = null; - } + Log.Debug("Custom format cache does not exist; proceeding without it"); + return; } - else + + Log.Debug("Loaded Cache"); + + // If the version is higher OR lower, we invalidate the cache. It means there's an + // incompatibility that we do not support. + if (CfCache.Version != CustomFormatCache.LatestVersion) { - Log.Debug("Custom format cache does not exist; proceeding without it"); + Log.Information("Cache version mismatch ({OldVersion} vs {LatestVersion}); ignoring cache data", + CfCache.Version, CustomFormatCache.LatestVersion); + CfCache = null; } } diff --git a/src/TrashLib/Radarr/CustomFormat/Cache/CachePersisterFactory.cs b/src/TrashLib/Radarr/CustomFormat/Cache/CachePersisterFactory.cs new file mode 100644 index 00000000..f3512d62 --- /dev/null +++ b/src/TrashLib/Radarr/CustomFormat/Cache/CachePersisterFactory.cs @@ -0,0 +1,25 @@ +using System; +using TrashLib.Cache; +using TrashLib.Config; + +namespace TrashLib.Radarr.CustomFormat.Cache +{ + internal class CachePersisterFactory : ICachePersisterFactory + { + private readonly Func _guidBuilderFactory; + private readonly Func _persisterFactory; + + public CachePersisterFactory( + Func guidBuilderFactory, + Func persisterFactory) + { + _guidBuilderFactory = guidBuilderFactory; + _persisterFactory = persisterFactory; + } + + public ICachePersister Create(IServiceConfiguration config) + { + return _persisterFactory(_guidBuilderFactory(config)); + } + } +} diff --git a/src/TrashLib/Radarr/CustomFormat/ICachePersister.cs b/src/TrashLib/Radarr/CustomFormat/Cache/ICachePersister.cs similarity index 88% rename from src/TrashLib/Radarr/CustomFormat/ICachePersister.cs rename to src/TrashLib/Radarr/CustomFormat/Cache/ICachePersister.cs index bb6ecdf9..8630c8ba 100644 --- a/src/TrashLib/Radarr/CustomFormat/ICachePersister.cs +++ b/src/TrashLib/Radarr/CustomFormat/Cache/ICachePersister.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using TrashLib.Radarr.CustomFormat.Models; using TrashLib.Radarr.CustomFormat.Models.Cache; -namespace TrashLib.Radarr.CustomFormat +namespace TrashLib.Radarr.CustomFormat.Cache { public interface ICachePersister { diff --git a/src/TrashLib/Radarr/CustomFormat/Cache/ICachePersisterFactory.cs b/src/TrashLib/Radarr/CustomFormat/Cache/ICachePersisterFactory.cs new file mode 100644 index 00000000..75610a0d --- /dev/null +++ b/src/TrashLib/Radarr/CustomFormat/Cache/ICachePersisterFactory.cs @@ -0,0 +1,9 @@ +using TrashLib.Config; + +namespace TrashLib.Radarr.CustomFormat.Cache +{ + public interface ICachePersisterFactory + { + ICachePersister Create(IServiceConfiguration config); + } +} diff --git a/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs b/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs index 21752de0..6c298d87 100644 --- a/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs +++ b/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Common.Extensions; using Serilog; using TrashLib.Radarr.Config; +using TrashLib.Radarr.CustomFormat.Cache; using TrashLib.Radarr.CustomFormat.Processors; using TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps; @@ -29,7 +30,7 @@ namespace TrashLib.Radarr.CustomFormat private ILogger Log { get; } - public async Task Process(bool isPreview, RadarrConfiguration config) + public async Task Process(bool isPreview, RadarrConfig config) { _cache.Load(); @@ -135,7 +136,7 @@ namespace TrashLib.Radarr.CustomFormat } } - private bool ValidateGuideDataAndCheckShouldProceed(RadarrConfiguration config) + private bool ValidateGuideDataAndCheckShouldProceed(RadarrConfig config) { Console.WriteLine(""); diff --git a/src/TrashLib/Radarr/CustomFormat/ICustomFormatUpdater.cs b/src/TrashLib/Radarr/CustomFormat/ICustomFormatUpdater.cs index 58fd89f0..18a4a4ba 100644 --- a/src/TrashLib/Radarr/CustomFormat/ICustomFormatUpdater.cs +++ b/src/TrashLib/Radarr/CustomFormat/ICustomFormatUpdater.cs @@ -5,6 +5,6 @@ namespace TrashLib.Radarr.CustomFormat { public interface ICustomFormatUpdater { - Task Process(bool isPreview, RadarrConfiguration config); + Task Process(bool isPreview, RadarrConfig config); } } diff --git a/src/TrashLib/Radarr/CustomFormat/Models/Cache/CustomFormatCache.cs b/src/TrashLib/Radarr/CustomFormat/Models/Cache/CustomFormatCache.cs index 990715d4..e1ba0d2e 100644 --- a/src/TrashLib/Radarr/CustomFormat/Models/Cache/CustomFormatCache.cs +++ b/src/TrashLib/Radarr/CustomFormat/Models/Cache/CustomFormatCache.cs @@ -14,15 +14,13 @@ namespace TrashLib.Radarr.CustomFormat.Models.Cache public class TrashIdMapping { - public TrashIdMapping(string trashId, string customFormatName, int customFormatId = default) + public TrashIdMapping(string trashId, int customFormatId) { - CustomFormatName = customFormatName; TrashId = trashId; CustomFormatId = customFormatId; } - public string CustomFormatName { get; set; } public string TrashId { get; } - public int CustomFormatId { get; set; } + public int CustomFormatId { get; } } } diff --git a/src/TrashLib/Radarr/CustomFormat/Models/ProcessedCustomFormatData.cs b/src/TrashLib/Radarr/CustomFormat/Models/ProcessedCustomFormatData.cs index 564788f8..ef6b1c7d 100644 --- a/src/TrashLib/Radarr/CustomFormat/Models/ProcessedCustomFormatData.cs +++ b/src/TrashLib/Radarr/CustomFormat/Models/ProcessedCustomFormatData.cs @@ -20,12 +20,9 @@ namespace TrashLib.Radarr.CustomFormat.Models public JObject Json { get; set; } public TrashIdMapping? CacheEntry { get; set; } - public string CacheAwareName => CacheEntry?.CustomFormatName ?? Name; - public void SetCache(int customFormatId) { - CacheEntry ??= new TrashIdMapping(TrashId, Name); - CacheEntry.CustomFormatId = customFormatId; + CacheEntry = new TrashIdMapping(TrashId, customFormatId); } [SuppressMessage("Microsoft.Design", "CA1024", Justification = "Method throws an exception")] diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs deleted file mode 100644 index 8e0b86fe..00000000 --- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Serilog; -using TrashLib.Radarr.Config; -using TrashLib.Radarr.CustomFormat.Guide; -using TrashLib.Radarr.CustomFormat.Models; -using TrashLib.Radarr.CustomFormat.Models.Cache; -using TrashLib.Radarr.CustomFormat.Processors.GuideSteps; - -namespace TrashLib.Radarr.CustomFormat.Processors -{ - public interface IGuideProcessorSteps - { - ICustomFormatStep CustomFormat { get; } - IConfigStep Config { get; } - IQualityProfileStep QualityProfile { get; } - } - - internal class GuideProcessor : IGuideProcessor - { - private readonly IRadarrGuideService _guideService; - private readonly Func _stepsFactory; - private IList? _guideCustomFormatJson; - private IGuideProcessorSteps _steps; - - public GuideProcessor(ILogger log, IRadarrGuideService guideService, Func stepsFactory) - { - _guideService = guideService; - _stepsFactory = stepsFactory; - Log = log; - _steps = stepsFactory(); - } - - private ILogger Log { get; } - - public IReadOnlyCollection ProcessedCustomFormats - => _steps.CustomFormat.ProcessedCustomFormats; - - public IReadOnlyCollection CustomFormatsNotInGuide - => _steps.Config.CustomFormatsNotInGuide; - - public IReadOnlyCollection ConfigData - => _steps.Config.ConfigData; - - public IDictionary ProfileScores - => _steps.QualityProfile.ProfileScores; - - public IReadOnlyCollection<(string name, string trashId, string profileName)> CustomFormatsWithoutScore - => _steps.QualityProfile.CustomFormatsWithoutScore; - - public IReadOnlyCollection DeletedCustomFormatsInCache - => _steps.CustomFormat.DeletedCustomFormatsInCache; - - public List<(string, string)> CustomFormatsWithOutdatedNames - => _steps.CustomFormat.CustomFormatsWithOutdatedNames; - - public Dictionary> DuplicatedCustomFormats - => _steps.CustomFormat.DuplicatedCustomFormats; - - public async Task BuildGuideData(IReadOnlyList config, CustomFormatCache? cache) - { - if (_guideCustomFormatJson == null) - { - Log.Debug("Requesting and parsing guide markdown"); - _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(_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 - // we call the Radarr API - - // Step 2: Use the processed custom formats from step 1 to process the configuration. - // CFs in config not in the guide are filtered out. - // Actual CF objects are associated to the quality profile objects to reduce lookups - _steps.Config.Process(_steps.CustomFormat.ProcessedCustomFormats, config); - - // Step 3: Use the processed config (which contains processed CFs) to process the quality profile scores. - // Score precedence logic is utilized here to decide the CF score per profile (same CF can actually have - // different scores depending on which profile it goes into). - _steps.QualityProfile.Process(_steps.Config.ConfigData); - } - - public void Reset() - { - _steps = _stepsFactory(); - } - } -} diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ConfigStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ConfigStep.cs deleted file mode 100644 index e724aa32..00000000 --- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ConfigStep.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Common.Extensions; -using MoreLinq.Extensions; -using TrashLib.Radarr.Config; -using TrashLib.Radarr.CustomFormat.Models; - -namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps -{ - internal class ConfigStep : IConfigStep - { - public List CustomFormatsNotInGuide { get; } = new(); - public List ConfigData { get; } = new(); - - public void Process(IReadOnlyCollection processedCfs, - IEnumerable config) - { - foreach (var singleConfig in config) - { - var validCfs = new List(); - - foreach (var name in singleConfig.Names) - { - var match = FindCustomFormatByName(processedCfs, name); - if (match == null) - { - CustomFormatsNotInGuide.Add(name); - } - else - { - validCfs.Add(match); - } - } - - foreach (var trashId in singleConfig.TrashIds) - { - var match = processedCfs.FirstOrDefault(cf => cf.TrashId.EqualsIgnoreCase(trashId)); - if (match == null) - { - CustomFormatsNotInGuide.Add(trashId); - } - else - { - validCfs.Add(match); - } - } - - ConfigData.Add(new ProcessedConfigData - { - QualityProfiles = singleConfig.QualityProfiles, - CustomFormats = validCfs - .DistinctBy(cf => cf.TrashId, StringComparer.InvariantCultureIgnoreCase) - .ToList() - }); - } - } - - private static ProcessedCustomFormatData? FindCustomFormatByName( - IReadOnlyCollection processedCfs, string name) - { - return processedCfs.FirstOrDefault( - cf => cf.CacheEntry?.CustomFormatName.EqualsIgnoreCase(name) ?? false) ?? - processedCfs.FirstOrDefault( - cf => cf.Name.EqualsIgnoreCase(name)); - } - } -} diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatProcessor.cs new file mode 100644 index 00000000..c9ac1944 --- /dev/null +++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatProcessor.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using TrashLib.Radarr.Config; +using TrashLib.Radarr.CustomFormat.Models; +using TrashLib.Radarr.CustomFormat.Models.Cache; + +namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps +{ + internal class CustomFormatProcessor : ICustomFormatProcessor + { + public List<(string, string)> CustomFormatsWithOutdatedNames { get; } = new(); + public List ProcessedCustomFormats { get; } = new(); + public List DeletedCustomFormatsInCache { get; } = new(); + + public void Process(IEnumerable customFormatGuideData, RadarrConfig config, CustomFormatCache? cache) + { + var processedCfs = customFormatGuideData + .Select(jsonData => ProcessCustomFormatData(jsonData, cache)) + .ToList(); + + // For each ID listed under the `trash_ids` YML property, match it to an existing CF + ProcessedCustomFormats.AddRange(config.CustomFormats + .Select(c => c.TrashId) + .Distinct() + .Join(processedCfs, + id => id, + cf => cf.TrashId, + (_, cf) => cf, + StringComparer.InvariantCultureIgnoreCase)); + + // Orphaned entries in cache represent custom formats we need to delete. + ProcessDeletedCustomFormats(cache); + } + + private static ProcessedCustomFormatData ProcessCustomFormatData(string guideData, CustomFormatCache? cache) + { + JObject obj = JObject.Parse(guideData); + var name = (string) obj["name"]; + var trashId = (string) obj["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) + { + Score = finalScore, + CacheEntry = cache?.TrashIdMappings.FirstOrDefault(c => c.TrashId == trashId) + }; + } + + private void ProcessDeletedCustomFormats(CustomFormatCache? cache) + { + if (cache == null) + { + return; + } + + static bool MatchCfInCache(ProcessedCustomFormatData cf, TrashIdMapping c) + => cf.CacheEntry != null && cf.CacheEntry.TrashId == c.TrashId; + + // Delete if CF is in cache and not in the guide or config + DeletedCustomFormatsInCache.AddRange(cache.TrashIdMappings + .Where(c => !ProcessedCustomFormats.Any(cf => MatchCfInCache(cf, c)))); + } + } +} diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs deleted file mode 100644 index 610073e5..00000000 --- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Common.Extensions; -using Newtonsoft.Json.Linq; -using TrashLib.Radarr.Config; -using TrashLib.Radarr.CustomFormat.Models; -using TrashLib.Radarr.CustomFormat.Models.Cache; - -namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps -{ - internal class CustomFormatStep : ICustomFormatStep - { - public List<(string, string)> CustomFormatsWithOutdatedNames { get; } = new(); - public List ProcessedCustomFormats { get; } = new(); - public List DeletedCustomFormatsInCache { get; } = new(); - - public Dictionary> DuplicatedCustomFormats { get; private set; } = - new(); - - public void Process(IEnumerable customFormatGuideData, - IReadOnlyCollection config, CustomFormatCache? cache) - { - var processedCfs = customFormatGuideData - .Select(cf => ProcessCustomFormatData(cf, cache)) - .ToList(); - - // For each ID listed under the `trash_ids` YML property, match it to an existing CF - ProcessedCustomFormats.AddRange(config - .SelectMany(c => c.TrashIds) - .Distinct(StringComparer.CurrentCultureIgnoreCase) - .Join(processedCfs, - id => id, - cf => cf.TrashId, - (_, cf) => cf, - StringComparer.InvariantCultureIgnoreCase)); - - // Build a list of CF names under the `names` property in YAML. Exclude any names that - // are already provided by the `trash_ids` property. - var allConfigCfNames = config - .SelectMany(c => c.Names) - .Distinct(StringComparer.CurrentCultureIgnoreCase) - .Where(n => !ProcessedCustomFormats.Any(cf => cf.CacheAwareName.EqualsIgnoreCase(n))) - .ToList(); - - // Perform updates and deletions based on matches in the cache. Matches in the cache are by ID. - foreach (var cf in processedCfs) - { - // Does the name of the CF in the guide match a name in the config? If yes, we keep it. - var configName = allConfigCfNames.FirstOrDefault(n => n.EqualsIgnoreCase(cf.Name)); - if (configName != null) - { - if (cf.CacheEntry != null) - { - // The cache entry might be using an old name. This will happen if: - // - A user has synced this CF before, AND - // - The name of the CF in the guide changed, AND - // - The user updated the name in their config to match the name in the guide. - cf.CacheEntry.CustomFormatName = cf.Name; - } - - ProcessedCustomFormats.Add(cf); - continue; - } - - // Does the name of the CF in the cache match a name in the config? If yes, we keep it. - configName = allConfigCfNames.FirstOrDefault(n => n.EqualsIgnoreCase(cf.CacheEntry?.CustomFormatName)); - if (configName != null) - { - // Config name is out of sync with the guide and should be updated - CustomFormatsWithOutdatedNames.Add((configName, cf.Name)); - ProcessedCustomFormats.Add(cf); - } - - // If we get here, we can't find a match in the config using cache or guide name, so the user must have - // removed it from their config. This will get marked for deletion later. - } - - // Orphaned entries in cache represent custom formats we need to delete. - ProcessDeletedCustomFormats(cache); - - // Check for multiple custom formats with the same name in the guide data (e.g. "DoVi") - ProcessDuplicates(); - } - - private void ProcessDuplicates() - { - DuplicatedCustomFormats = ProcessedCustomFormats - .GroupBy(cf => cf.Name) - .Where(grp => grp.Count() > 1) - .ToDictionary(grp => grp.Key, grp => grp.ToList()); - - ProcessedCustomFormats.RemoveAll(cf => DuplicatedCustomFormats.ContainsKey(cf.Name)); - } - - private static ProcessedCustomFormatData ProcessCustomFormatData(string guideData, CustomFormatCache? cache) - { - JObject obj = JObject.Parse(guideData); - var name = (string) obj["name"]; - var trashId = (string) obj["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) - { - Score = finalScore, - CacheEntry = cache?.TrashIdMappings.FirstOrDefault(c => c.TrashId == trashId) - }; - } - - private void ProcessDeletedCustomFormats(CustomFormatCache? cache) - { - if (cache == null) - { - return; - } - - static bool MatchCfInCache(ProcessedCustomFormatData cf, TrashIdMapping c) - => cf.CacheEntry != null && cf.CacheEntry.TrashId == c.TrashId; - - // Delete if CF is in cache and not in the guide or config - DeletedCustomFormatsInCache.AddRange(cache.TrashIdMappings - .Where(c => !ProcessedCustomFormats.Any(cf => MatchCfInCache(cf, c)))); - } - } -} diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/IConfigStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/IConfigStep.cs deleted file mode 100644 index 774bbd54..00000000 --- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/IConfigStep.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using TrashLib.Radarr.Config; -using TrashLib.Radarr.CustomFormat.Models; - -namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps -{ - public interface IConfigStep - { - List CustomFormatsNotInGuide { get; } - List ConfigData { get; } - - void Process(IReadOnlyCollection processedCfs, - IEnumerable config); - } -} diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatProcessor.cs similarity index 61% rename from src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs rename to src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatProcessor.cs index 5ccd1af5..f484b050 100644 --- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs +++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatProcessor.cs @@ -5,14 +5,12 @@ using TrashLib.Radarr.CustomFormat.Models.Cache; namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps { - public interface ICustomFormatStep + public interface ICustomFormatProcessor { + List<(string, string)> CustomFormatsWithOutdatedNames { get; } List ProcessedCustomFormats { get; } List DeletedCustomFormatsInCache { get; } - List<(string, string)> CustomFormatsWithOutdatedNames { get; } - Dictionary> DuplicatedCustomFormats { get; } - void Process(IEnumerable customFormatGuideData, - IReadOnlyCollection config, CustomFormatCache? cache); + void Process(IEnumerable customFormatGuideData, RadarrConfig config, CustomFormatCache? cache); } } diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/IGuideProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/IGuideProcessor.cs deleted file mode 100644 index 66a5b2e5..00000000 --- a/src/TrashLib/Radarr/CustomFormat/Processors/IGuideProcessor.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using TrashLib.Radarr.Config; -using TrashLib.Radarr.CustomFormat.Models; -using TrashLib.Radarr.CustomFormat.Models.Cache; - -namespace TrashLib.Radarr.CustomFormat.Processors -{ - internal interface IGuideProcessor - { - IReadOnlyCollection ProcessedCustomFormats { get; } - IReadOnlyCollection CustomFormatsNotInGuide { get; } - IReadOnlyCollection ConfigData { get; } - IDictionary ProfileScores { get; } - IReadOnlyCollection<(string name, string trashId, string profileName)> CustomFormatsWithoutScore { get; } - IReadOnlyCollection DeletedCustomFormatsInCache { get; } - List<(string, string)> CustomFormatsWithOutdatedNames { get; } - Dictionary> DuplicatedCustomFormats { get; } - - Task BuildGuideData(IReadOnlyList config, CustomFormatCache? cache); - void Reset(); - } -} diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/IPersistenceProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/IPersistenceProcessor.cs index 1aaa54b4..c31e7fc0 100644 --- a/src/TrashLib/Radarr/CustomFormat/Processors/IPersistenceProcessor.cs +++ b/src/TrashLib/Radarr/CustomFormat/Processors/IPersistenceProcessor.cs @@ -14,7 +14,7 @@ namespace TrashLib.Radarr.CustomFormat.Processors CustomFormatTransactionData Transactions { get; } Task PersistCustomFormats( - RadarrConfiguration config, + RadarrConfig config, IEnumerable guideCfs, IEnumerable deletedCfsInCache, IDictionary profileScores); diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/PersistenceProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/PersistenceProcessor.cs index 2567d00c..729aa60b 100644 --- a/src/TrashLib/Radarr/CustomFormat/Processors/PersistenceProcessor.cs +++ b/src/TrashLib/Radarr/CustomFormat/Processors/PersistenceProcessor.cs @@ -49,7 +49,7 @@ namespace TrashLib.Radarr.CustomFormat.Processors } public async Task PersistCustomFormats( - RadarrConfiguration config, + RadarrConfig config, IEnumerable guideCfs, IEnumerable deletedCfsInCache, IDictionary profileScores) diff --git a/src/TrashLib/Radarr/QualityDefinition/IRadarrQualityDefinitionUpdater.cs b/src/TrashLib/Radarr/QualityDefinition/IRadarrQualityDefinitionUpdater.cs index 38942bd6..c97f1afc 100644 --- a/src/TrashLib/Radarr/QualityDefinition/IRadarrQualityDefinitionUpdater.cs +++ b/src/TrashLib/Radarr/QualityDefinition/IRadarrQualityDefinitionUpdater.cs @@ -5,6 +5,6 @@ namespace TrashLib.Radarr.QualityDefinition { public interface IRadarrQualityDefinitionUpdater { - Task Process(bool isPreview, RadarrConfiguration config); + Task Process(bool isPreview, RadarrConfig config); } } diff --git a/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs b/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs index b1faaa5d..67b646fd 100644 --- a/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs +++ b/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs @@ -24,7 +24,7 @@ namespace TrashLib.Radarr.QualityDefinition private ILogger Log { get; } - public async Task Process(bool isPreview, RadarrConfiguration config) + public async Task Process(bool isPreview, RadarrConfig config) { Log.Information("Processing Quality Definition: {QualityDefinition}", config.QualityDefinition!.Type); var qualityDefinitions = _parser.ParseMarkdown(await _parser.GetMarkdownData()); diff --git a/src/TrashLib/Radarr/RadarrAutofacModule.cs b/src/TrashLib/Radarr/RadarrAutofacModule.cs index 05549672..1bd49f3b 100644 --- a/src/TrashLib/Radarr/RadarrAutofacModule.cs +++ b/src/TrashLib/Radarr/RadarrAutofacModule.cs @@ -1,11 +1,10 @@ -using System; using Autofac; using Autofac.Extras.AggregateService; -using TrashLib.Cache; using TrashLib.Config; using TrashLib.Radarr.Config; using TrashLib.Radarr.CustomFormat; using TrashLib.Radarr.CustomFormat.Api; +using TrashLib.Radarr.CustomFormat.Cache; using TrashLib.Radarr.CustomFormat.Guide; using TrashLib.Radarr.CustomFormat.Processors; using TrashLib.Radarr.CustomFormat.Processors.GuideSteps; @@ -17,25 +16,6 @@ namespace TrashLib.Radarr { public class RadarrAutofacModule : Module { - // class CachePersisterFactory - // { - // private readonly Func _guidBuilderFactory; - // private readonly Func _persisterFactory; - // - // public CachePersisterFactory( - // Func guidBuilderFactory, - // Func persisterFactory) - // { - // _guidBuilderFactory = guidBuilderFactory; - // _persisterFactory = persisterFactory; - // } - // - // public ICachePersister Create(IServiceConfiguration config) - // { - // return _persisterFactory(_guidBuilderFactory(config)); - // } - // } - protected override void Load(ContainerBuilder builder) { // Services @@ -54,20 +34,20 @@ namespace TrashLib.Radarr // Custom Format Support builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); - builder.Register>(c => config => - { - var guidBuilderFactory = c.Resolve>(); - return c.Resolve(TypedParameter.From(guidBuilderFactory(config))); - }); + // builder.Register>(c => config => + // { + // var guidBuilderFactory = c.Resolve>(); + // return c.Resolve(TypedParameter.From(guidBuilderFactory(config))); + // }); // Guide Processor // todo: register as singleton to avoid parsing guide multiple times when using 2 or more instances in config - builder.RegisterType().As(); - builder.RegisterAggregateService(); - builder.RegisterType().As(); - builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); // Persistence Processor diff --git a/src/recyclarr.sqlite b/src/recyclarr.sqlite new file mode 100644 index 00000000..e69de29b