From 8c7768891e3af0618742a3af58b65c5baf6b3d94 Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Sat, 7 Jan 2023 11:33:12 -0600 Subject: [PATCH] fix: Better handling of invalid CF cache entries Due to changes in v4.1.1, sometimes invalid cache entries (zero-value) were written to the cache. These are now treated as invalid and matches by name will be performed. Fixes #160 --- CHANGELOG.md | 4 ++ src/Recyclarr.TrashLib.TestLibrary/NewCf.cs | 33 ++++++++------ .../CustomFormat/CachePersisterTest.cs | 44 +++++++++++++------ .../Processors/GuideProcessorTest.cs | 4 +- .../GuideSteps/CustomFormatStepTest.cs | 35 +-------------- .../GuideSteps/QualityProfileStepTest.cs | 4 +- .../CustomFormatApiPersistenceStepTest.cs | 4 +- .../JsonTransactionStepTest.cs | 14 +++--- .../QualityProfileApiPersistenceStepTest.cs | 12 +++-- .../CustomFormat/Api/CustomFormatService.cs | 4 +- .../Services/CustomFormat/CachePersister.cs | 8 ++-- .../Models/Cache/CustomFormatCache.cs | 16 +------ .../Models/ProcessedCustomFormatData.cs | 16 +------ .../Processors/GuideSteps/CustomFormatStep.cs | 12 +++-- .../PersistenceSteps/JsonTransactionStep.cs | 21 +++------ .../QualityProfileApiPersistenceStep.cs | 3 +- 16 files changed, 96 insertions(+), 138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index baada0ad..a72a1564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- More scenarios were causing custom formats to sometimes not be synced (#160). + ## [4.1.2] - 2023-01-06 ### Fixed diff --git a/src/Recyclarr.TrashLib.TestLibrary/NewCf.cs b/src/Recyclarr.TrashLib.TestLibrary/NewCf.cs index e132eb0e..ded12dd4 100644 --- a/src/Recyclarr.TrashLib.TestLibrary/NewCf.cs +++ b/src/Recyclarr.TrashLib.TestLibrary/NewCf.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json.Linq; using Recyclarr.TrashLib.Services.CustomFormat.Models; -using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; namespace Recyclarr.TrashLib.TestLibrary; @@ -8,39 +7,45 @@ 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)); + return Data(name, trashId, score, JObject.Parse($"{{'name':'{name}'}}")); } - public static ProcessedCustomFormatData Processed(string name, string trashId, int? score = null) + public static CustomFormatData Data(string name, string trashId, int? score, JObject json) { - return new ProcessedCustomFormatData(Data(name, trashId, score)); + return new CustomFormatData("", name, trashId, score, json); } - public static ProcessedCustomFormatData Processed(string name, string trashId, int? score, JObject json) + public static ProcessedCustomFormatData ProcessedWithScore(string name, string trashId, int score, JObject json) { - return new ProcessedCustomFormatData(new CustomFormatData("", name, trashId, score, json)); + return new ProcessedCustomFormatData(Data(name, trashId, score, json)); + } + + public static ProcessedCustomFormatData ProcessedWithScore(string name, string trashId, int score, int formatId = 0) + { + return new ProcessedCustomFormatData(Data(name, trashId, score)) + { + FormatId = formatId + }; } public static ProcessedCustomFormatData Processed(string name, string trashId, JObject json) { - return Processed(name, trashId, null, json); + return Processed(name, trashId, 0, json); } - public static ProcessedCustomFormatData Processed(string name, string trashId, TrashIdMapping cacheEntry) + public static ProcessedCustomFormatData Processed(string name, string trashId, int formatId = 0) { return new ProcessedCustomFormatData(Data(name, trashId)) { - CacheEntry = cacheEntry + FormatId = formatId }; } - public static ProcessedCustomFormatData Processed(string name, string trashId, JObject json, - TrashIdMapping? cacheEntry) + public static ProcessedCustomFormatData Processed(string name, string trashId, int formatId, JObject json) { - return new ProcessedCustomFormatData(new CustomFormatData("", name, trashId, null, json)) + return new ProcessedCustomFormatData(Data(name, trashId, null, json)) { - CacheEntry = cacheEntry + FormatId = formatId }; } } diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/CachePersisterTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/CachePersisterTest.cs index 72c93b67..824c270d 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/CachePersisterTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/CachePersisterTest.cs @@ -6,7 +6,6 @@ using Recyclarr.TrashLib.Cache; using Recyclarr.TrashLib.Services.CustomFormat; using Recyclarr.TrashLib.Services.CustomFormat.Models; using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; -using Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; using Recyclarr.TrashLib.TestLibrary; using Serilog; @@ -106,31 +105,48 @@ public class CachePersisterTest var ctx = new Context(); // Load initial CfCache just to test that it gets replaced - var testCfObj = new CustomFormatCache + ctx.ServiceCache.Load().Returns(new CustomFormatCache { - TrashIdMappings = new Collection {new("", "") {CustomFormatId = 5}} - }; - ctx.ServiceCache.Load().Returns(testCfObj); + TrashIdMappings = new Collection {new("trashid", "", 1)} + }); ctx.Persister.Load(); // Update with new cached items - var results = new CustomFormatTransactionData(); - results.NewCustomFormats.Add(NewCf.Processed("cfname", "trashid", 10)); - var customFormatData = new List { - new(NewCf.Data("", "trashid")) {CacheEntry = new TrashIdMapping("trashid", "", 10)} + NewCf.Processed("trashid", "name", 5) }; - ctx.Persister.Update(customFormatData); + ctx.Persister.CfCache.Should().BeEquivalentTo(new CustomFormatCache { - TrashIdMappings = new Collection {customFormatData[0].CacheEntry!} + TrashIdMappings = new Collection + { + new(customFormatData[0].TrashId, customFormatData[0].Name, customFormatData[0].FormatId) + } }); + } - customFormatData.Should().ContainSingle() - .Which.CacheEntry.Should().BeEquivalentTo( - new TrashIdMapping("trashid", "") {CustomFormatId = 10}); + [Test] + public void Saving_skips_custom_formats_with_zero_id() + { + var ctx = new Context(); + + // Update with new cached items + var customFormatData = new List + { + NewCf.Processed("trashid1", "name", 5), + NewCf.Processed("trashid2", "invalid") + }; + ctx.Persister.Update(customFormatData); + + ctx.Persister.CfCache.Should().BeEquivalentTo(new CustomFormatCache + { + TrashIdMappings = new Collection + { + new(customFormatData[0].TrashId, customFormatData[0].Name, customFormatData[0].FormatId) + } + }); } [Test] diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideProcessorTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideProcessorTest.cs index 71c6584e..2f356168 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideProcessorTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideProcessorTest.cs @@ -99,9 +99,9 @@ public class GuideProcessorTest var expectedProcessedCustomFormatData = new List { - NewCf.Processed("Surround Sound", "43bb5f09c79641e7a22e48d440bd8868", 500, + NewCf.ProcessedWithScore("Surround Sound", "43bb5f09c79641e7a22e48d440bd8868", 500, ctx.ReadJson("ImportableCustomFormat1_Processed.json")), - NewCf.Processed("DTS-HD/DTS:X", "4eb3c272d48db8ab43c2c85283b69744", 480, + NewCf.ProcessedWithScore("DTS-HD/DTS:X", "4eb3c272d48db8ab43c2c85283b69744", 480, ctx.ReadJson("ImportableCustomFormat2_Processed.json")), NewCf.Processed("No Score", "abc") }; diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs index 015f585d..dfe78f57 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs @@ -82,13 +82,13 @@ public class CustomFormatStepTest var testCache = new CustomFormatCache { - TrashIdMappings = new Collection {new("id1000", "")} + TrashIdMappings = new Collection {new("id1000", "", 1)} }; processor.Process(guideData, testConfig, testCache); processor.DeletedCustomFormatsInCache.Should() - .BeEquivalentTo(new[] {new TrashIdMapping("id1000", "")}); + .BeEquivalentTo(new[] {new TrashIdMapping("id1000", "", 1)}); processor.ProcessedCustomFormats.Should().BeEquivalentTo(new List { NewCf.Processed("name1", "id1") @@ -152,35 +152,4 @@ public class CustomFormatStepTest processor.DeletedCustomFormatsInCache.Should().BeEmpty(); processor.ProcessedCustomFormats.Should().BeEmpty(); } - - [Test, AutoMockData] - public void Score_from_json_takes_precedence_over_score_from_guide(CustomFormatStep processor) - { - var guideData = new List - { - NewCf.Data("name1", "id1", 100) - }; - - var testConfig = new List - { - new() - { - TrashIds = new List {"id1"}, - QualityProfiles = new List - { - new() {Name = "profile", Score = 200} - } - } - }; - - processor.Process(guideData, testConfig, null); - - processor.DeletedCustomFormatsInCache.Should().BeEmpty(); - processor.ProcessedCustomFormats.Should() - .BeEquivalentTo(new List - { - NewCf.Processed("name1", "id1", 100) - }, - op => op.Using(new JsonEquivalencyStep())); - } } diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs index 6455c868..1887209a 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs @@ -74,7 +74,7 @@ public class QualityProfileStepTest { CustomFormats = new List { - NewCf.Processed("", "id1", 100) + NewCf.ProcessedWithScore("", "id1", 100) }, QualityProfiles = new List { @@ -109,7 +109,7 @@ public class QualityProfileStepTest { CustomFormats = new List { - NewCf.Processed("name1", "id1", 0) + NewCf.ProcessedWithScore("name1", "id1", 0) }, QualityProfiles = new List { diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs index 18252ad8..ee5e0ddc 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs @@ -14,7 +14,7 @@ public class CustomFormatApiPersistenceStepTest { private static ProcessedCustomFormatData QuickMakeCf(string cfName, string trashId, int cfId) { - return NewCf.Processed(cfName, trashId, new TrashIdMapping(trashId, cfName) {CustomFormatId = cfId}); + return NewCf.Processed(cfName, trashId, cfId); } [Test] @@ -24,7 +24,7 @@ public class CustomFormatApiPersistenceStepTest transactions.NewCustomFormats.Add(QuickMakeCf("cfname1", "trashid1", 1)); transactions.UpdatedCustomFormats.Add(QuickMakeCf("cfname2", "trashid2", 2)); transactions.UnchangedCustomFormats.Add(QuickMakeCf("cfname3", "trashid3", 3)); - transactions.DeletedCustomFormatIds.Add(new TrashIdMapping("trashid4", "cfname4") {CustomFormatId = 4}); + transactions.DeletedCustomFormatIds.Add(new TrashIdMapping("trashid4", "cfname4", 4)); var api = Substitute.For(); diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/JsonTransactionStepTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/JsonTransactionStepTest.cs index 849ac5bf..375c0530 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/JsonTransactionStepTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/JsonTransactionStepTest.cs @@ -122,8 +122,8 @@ public class JsonTransactionStepTest var guideCfs = new List { NewCf.Processed("created", "id1", guideCfData[0]), - NewCf.Processed("updated_different_name", "id2", guideCfData[1], new TrashIdMapping("id2", "", 2)), - NewCf.Processed("no_change", "id3", guideCfData[2], new TrashIdMapping("id3", "", 3)) + NewCf.Processed("updated_different_name", "id2", 2, guideCfData[1]), + NewCf.Processed("no_change", "id3", 3, guideCfData[2]) }; processor.Process(guideCfs, radarrCfs); @@ -231,12 +231,12 @@ public class JsonTransactionStepTest }"); var deletedCfsInCache = new List { - new("", "") {CustomFormatId = 2} + new("", "", 1) {CustomFormatId = 2} }; var guideCfs = new List { - NewCf.Processed("updated", "", guideCfData, new TrashIdMapping("", "") {CustomFormatId = 1}) + NewCf.Processed("updated", "", 1, guideCfData) }; var radarrCfs = JsonConvert.DeserializeObject>(radarrCfData); @@ -324,7 +324,7 @@ public class JsonTransactionStepTest var guideCfs = new List { - NewCf.Processed("first", "", new TrashIdMapping("", "first", 2)) + NewCf.Processed("first", "", 2) }; processor.Process(guideCfs, serviceCfs); @@ -353,7 +353,7 @@ public class JsonTransactionStepTest processor.Process(guideCfs, serviceCfs); processor.Transactions.UpdatedCustomFormats.Should().BeEquivalentTo( - new[] {NewCf.Processed("first", "", new TrashIdMapping("", "first", 1))}, - o => o.Including(x => x.CacheEntry!.CustomFormatId)); + new[] {NewCf.Processed("first", "", 1)}, + o => o.Including(x => x.FormatId)); } } diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs index eb102f0a..e1f36e73 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using Recyclarr.TestLibrary.NSubstitute; using Recyclarr.TrashLib.Services.CustomFormat.Api; using Recyclarr.TrashLib.Services.CustomFormat.Models; -using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; using Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; using Recyclarr.TrashLib.TestLibrary; @@ -47,8 +46,7 @@ public class QualityProfileApiPersistenceStepTest var cfScores = new Dictionary { { - "profile1", CfTestUtils.NewMapping(new FormatMappingEntry( - NewCf.Processed("", "", new TrashIdMapping("", "") {CustomFormatId = 4}), 100)) + "profile1", CfTestUtils.NewMapping(new FormatMappingEntry(NewCf.Processed("", "", 4), 100)) } }; @@ -109,7 +107,7 @@ public class QualityProfileApiPersistenceStepTest { { "profile1", CfTestUtils.NewMappingWithReset( - new FormatMappingEntry(NewCf.Processed("", "", new TrashIdMapping("", "", 2)), 100)) + new FormatMappingEntry(NewCf.Processed("", "", 2), 100)) } }; @@ -183,11 +181,11 @@ public class QualityProfileApiPersistenceStepTest { "profile1", CfTestUtils.NewMapping( // First match by ID - new FormatMappingEntry(NewCf.Processed("", "", new TrashIdMapping("", "", 4)), 100), + new FormatMappingEntry(NewCf.Processed("", "", 4), 100), // Should NOT match because we do not use names to assign scores - new FormatMappingEntry(NewCf.Processed("", "", new TrashIdMapping("", "")), 101), + new FormatMappingEntry(NewCf.Processed("", ""), 101), // Second match by ID - new FormatMappingEntry(NewCf.Processed("", "", new TrashIdMapping("", "", 1)), 102)) + new FormatMappingEntry(NewCf.Processed("", "", 1), 102)) } }; diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/CustomFormatService.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/CustomFormatService.cs index 64223b1d..1a04d466 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/CustomFormatService.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/CustomFormatService.cs @@ -28,13 +28,13 @@ internal class CustomFormatService : ICustomFormatService if (response != null) { - cf.SetCache(response.Value("id")); + cf.FormatId = response.Value("id"); } } public async Task UpdateCustomFormat(ProcessedCustomFormatData cf) { - await _service.Request("customformat", cf.GetCustomFormatId()) + await _service.Request("customformat", cf.FormatId) .PutJsonAsync(cf.Json) .ReceiveJson(); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/CachePersister.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/CachePersister.cs index d5816bfb..2f74485c 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/CachePersister.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/CachePersister.cs @@ -56,10 +56,12 @@ public class CachePersister : ICachePersister public void Update(IEnumerable customFormats) { - Log.Debug("Updating cache"); CfCache = new CustomFormatCache(); CfCache!.TrashIdMappings.AddRange(customFormats - .Where(cf => cf.CacheEntry != null) - .Select(cf => cf.CacheEntry!)); + .Where(cf => cf.FormatId is not 0) + .Select(cf => new TrashIdMapping(cf.TrashId, cf.Name, cf.FormatId))); + + Log.Debug("Updated Cache with {Mappings}", CfCache.TrashIdMappings.Select( + x => $"TrashID: {x.TrashId}, Name: {x.CustomFormatName}, FormatID: {x.CustomFormatId}")); } } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Models/Cache/CustomFormatCache.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Models/Cache/CustomFormatCache.cs index 3dd2bb95..778f8a99 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Models/Cache/CustomFormatCache.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Models/Cache/CustomFormatCache.cs @@ -4,7 +4,7 @@ using Recyclarr.TrashLib.Cache; namespace Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; [CacheObjectName("custom-format-cache")] -public class CustomFormatCache +public record CustomFormatCache { public const int LatestVersion = 1; @@ -12,16 +12,4 @@ public class CustomFormatCache public Collection TrashIdMappings { get; init; } = new(); } -public class TrashIdMapping -{ - public TrashIdMapping(string trashId, string customFormatName, int customFormatId = default) - { - TrashId = trashId; - CustomFormatName = customFormatName; - CustomFormatId = customFormatId; - } - - public string TrashId { get; } - public string CustomFormatName { get; } - public int CustomFormatId { get; set; } -} +public record TrashIdMapping(string TrashId, string CustomFormatName, int CustomFormatId); diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Models/ProcessedCustomFormatData.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Models/ProcessedCustomFormatData.cs index 8876eb29..43e95096 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Models/ProcessedCustomFormatData.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Models/ProcessedCustomFormatData.cs @@ -1,6 +1,4 @@ -using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json.Linq; -using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; namespace Recyclarr.TrashLib.Services.CustomFormat.Models; @@ -18,17 +16,5 @@ public class ProcessedCustomFormatData public string TrashId => _data.TrashId; public int? Score => _data.Score; public JObject Json { get; set; } - public TrashIdMapping? CacheEntry { get; set; } - - public void SetCache(int customFormatId) - { - // Do not pass the customFormatId to constructor since an instance may already exist. - CacheEntry ??= new TrashIdMapping(TrashId, Name); - CacheEntry.CustomFormatId = customFormatId; - } - - [SuppressMessage("Microsoft.Design", "CA1024", Justification = "Method throws an exception")] - public int GetCustomFormatId() - => CacheEntry?.CustomFormatId ?? - throw new InvalidOperationException("CacheEntry must exist to obtain custom format ID"); + public int FormatId { get; set; } } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs index c0d4721b..40a5bfa4 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs @@ -24,7 +24,7 @@ public class CustomFormatStep : ICustomFormatStep // 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) + .Distinct(StringComparer.InvariantCultureIgnoreCase) .Join(processedCfs, id => id, cf => cf.TrashId, @@ -38,9 +38,10 @@ public class CustomFormatStep : ICustomFormatStep private static ProcessedCustomFormatData ProcessCustomFormatData(CustomFormatData cf, CustomFormatCache? cache) { + var map = cache?.TrashIdMappings.FirstOrDefault(c => c.TrashId == cf.TrashId); return new ProcessedCustomFormatData(cf) { - CacheEntry = cache?.TrashIdMappings.FirstOrDefault(c => c.TrashId == cf.TrashId) + FormatId = map?.CustomFormatId ?? 0 }; } @@ -51,11 +52,8 @@ public class CustomFormatStep : ICustomFormatStep 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)))); + _deletedCustomFormatsInCache.AddRange( + cache.TrashIdMappings.Where(map => ProcessedCustomFormats.All(cf => cf.TrashId != map.TrashId))); } } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/JsonTransactionStep.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/JsonTransactionStep.cs index 16102abd..60048551 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/JsonTransactionStep.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/JsonTransactionStep.cs @@ -35,8 +35,8 @@ public class JsonTransactionStep : IJsonTransactionStep continue; } - // If cache entry is NOT null, that means we found the service by its ID - if (guideCf.CacheEntry is not null) + // If custom format ID is NOT zero, that means we found the serviceCf by its ID + if (guideCf.FormatId != 0) { // Check for conflicts with upstream CFs with the same name but different ID. // If found, it is recorded and we skip this CF. @@ -45,15 +45,8 @@ public class JsonTransactionStep : IJsonTransactionStep continue; } } - // Null cache entry use case - else - { - // Set the cache for use later (like updating scores) if it hasn't been updated already. - // This handles CFs that already exist in the service but aren't cached (they will be added to cache - // later). - guideCf.SetCache(serviceCf.Value("id")); - } + guideCf.FormatId = serviceCf.Value("id"); guideCf.Json = (JObject) serviceCf.DeepClone(); UpdateServiceCf(guideCf.Json, guideCfJson); @@ -73,7 +66,7 @@ public class JsonTransactionStep : IJsonTransactionStep ProcessedCustomFormatData guideCf, JObject serviceCf) { - var conflictingServiceCf = FindServiceCf(serviceCfs, null, guideCf.Name); + var conflictingServiceCf = FindServiceCf(serviceCfs, 0, guideCf.Name); if (conflictingServiceCf is null) { return false; @@ -103,15 +96,15 @@ public class JsonTransactionStep : IJsonTransactionStep private static JObject? FindServiceCf(IReadOnlyCollection serviceCfs, ProcessedCustomFormatData guideCf) { - return FindServiceCf(serviceCfs, guideCf.CacheEntry?.CustomFormatId, guideCf.Name); + return FindServiceCf(serviceCfs, guideCf.FormatId, guideCf.Name); } - private static JObject? FindServiceCf(IReadOnlyCollection serviceCfs, int? cfId, string? cfName = null) + private static JObject? FindServiceCf(IReadOnlyCollection serviceCfs, int cfId, string? cfName = null) { JObject? match = null; // Try to find match in cache first - if (cfId is not null) + if (cfId is not 0) { match = serviceCfs.FirstOrDefault(rcf => cfId == rcf.Value("id")); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs index 8f496515..55ee6a66 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs @@ -79,7 +79,6 @@ internal class QualityProfileApiPersistenceStep : IQualityProfileApiPersistenceS QualityProfileCustomFormatScoreMapping scoreMap) { return scoreMap.Mapping.FirstOrDefault( - m => m.CustomFormat.CacheEntry != null && - formatItem.Value("format") == m.CustomFormat.CacheEntry.CustomFormatId); + m => formatItem.Value("format") == m.CustomFormat.FormatId); } }