fix: Merge CFs using Replace instead of Add

json-serializing-nullable-fields-issue
Robert Dailey 8 months ago
parent 5881b611cf
commit 9d351f99ed

@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Relaxed validation rules for `trash_ids` and `quality_profiles` under `custom_formats`. Both of
these nodes may now be empty. This is mostly to make commenting out parts of configuration
templates easier.
- The merge operation for `custom_formats` is now "Join" (previously "Add"). If, for the same
profile, you "reassign" a different score to a CF, the score now gets updated without having to
remove the CF from `custom_formats` sections in included YAML files.
## [5.4.2] - 2023-09-14

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Recyclarr.Common.Extensions;
namespace Recyclarr.TrashLib.Config.Parsing.PostProcessing.ConfigMerging;
@ -24,13 +25,13 @@ public abstract class ServiceConfigMerger<T> where T : ServiceConfigYaml
{
return a with
{
CustomFormats = Combine(a.CustomFormats, b.CustomFormats, (x, y) => x.Concat(y).ToList()),
CustomFormats = Combine(a.CustomFormats, b.CustomFormats, MergeCustomFormats),
QualityProfiles = MergeQualityProfiles(a.QualityProfiles, b.QualityProfiles),
QualityDefinition = Combine(a.QualityDefinition, b.QualityDefinition,
(x, y) => x with
(a1, b1) => a1 with
{
Type = y.Type ?? x.Type,
PreferredRatio = y.PreferredRatio ?? x.PreferredRatio
Type = b1.Type ?? a1.Type,
PreferredRatio = b1.PreferredRatio ?? a1.PreferredRatio
}),
DeleteOldCustomFormats =
@ -41,6 +42,48 @@ public abstract class ServiceConfigMerger<T> where T : ServiceConfigYaml
};
}
private sealed record FlattenedCfs(string? ProfileName, int? Score, IReadOnlyCollection<string> TrashIds);
private static IReadOnlyCollection<CustomFormatConfigYaml> MergeCustomFormats(
IReadOnlyCollection<CustomFormatConfigYaml> a,
IReadOnlyCollection<CustomFormatConfigYaml> b)
{
var flattenedA = FlattenCfs(a);
var flattenedB = FlattenCfs(b);
return flattenedA
// This builds a list of TrashIds in side B that are assigned to matching profiles in A
.Select(x => (A: x, B: flattenedB
.Where(y => y.ProfileName.EqualsIgnoreCase(x.ProfileName)) // Ignore score
.SelectMany(y => y.TrashIds)
.Distinct(StringComparer.InvariantCultureIgnoreCase)
.ToList()))
// Add everything on side A that isn't on side B
.Select(x => new CustomFormatConfigYaml
{
TrashIds = x.A.TrashIds
.Except(x.B, StringComparer.InvariantCultureIgnoreCase)
.ToList(),
QualityProfiles = x.A.ProfileName is not null
? new[] {new QualityScoreConfigYaml {Name = x.A.ProfileName, Score = x.A.Score}}
: null
})
.Concat(b)
.ToList();
static List<FlattenedCfs> FlattenCfs(IEnumerable<CustomFormatConfigYaml> cfs)
{
return cfs
.Where(x => x.TrashIds is not null)
.SelectMany(x => x is {QualityProfiles.Count: > 0}
? x.QualityProfiles.Select(y => new FlattenedCfs(y.Name, y.Score, x.TrashIds!))
: new[] {new FlattenedCfs(null, null, x.TrashIds!)})
.GroupBy(x => (Name: x.ProfileName, x.Score))
.Select(x => new FlattenedCfs(x.Key.Name, x.Key.Score, x.SelectMany(y => y.TrashIds).ToList()))
.ToList();
}
}
private static IReadOnlyCollection<QualityProfileConfigYaml>? MergeQualityProfiles(
IReadOnlyCollection<QualityProfileConfigYaml>? a,
IReadOnlyCollection<QualityProfileConfigYaml>? b)

@ -263,7 +263,17 @@ public class ServiceConfigMergerTest
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 100}
new QualityScoreConfigYaml {Name = "c", Score = 100},
new QualityScoreConfigYaml {Name = "d", Score = 101},
new QualityScoreConfigYaml {Name = "e", Score = 102}
}
},
new CustomFormatConfigYaml
{
TrashIds = new[] {"id2"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "f", Score = 100}
}
}
}
@ -288,6 +298,14 @@ public class ServiceConfigMergerTest
{
new QualityScoreConfigYaml {Name = "e", Score = 300}
}
},
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 50}
}
}
}
};
@ -298,7 +316,65 @@ public class ServiceConfigMergerTest
result.Should().BeEquivalentTo(new SonarrConfigYaml
{
CustomFormats = leftConfig.CustomFormats.Concat(rightConfig.CustomFormats).ToList()
CustomFormats = new[]
{
new CustomFormatConfigYaml
{
TrashIds = new[] {"id2"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 100}
}
},
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "d", Score = 101}
}
},
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "e", Score = 102}
}
},
new CustomFormatConfigYaml
{
TrashIds = new[] {"id2"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "f", Score = 100}
}
},
new CustomFormatConfigYaml
{
TrashIds = new[] {"id3", "id4"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "d", Score = 200}
}
},
new CustomFormatConfigYaml
{
TrashIds = new[] {"id5", "id6"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "e", Score = 300}
}
},
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1"},
QualityProfiles = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 50}
}
}
}
});
}

@ -164,9 +164,21 @@ public class IncludePostProcessorIntegrationTest : ConfigIntegrationFixture
Include = null,
CustomFormats = new[]
{
new CustomFormatConfigYaml {TrashIds = new[] {"496f355514737f7d83bf7aa4d24f8169"}},
new CustomFormatConfigYaml {TrashIds = new[] {"2f22d89048b01681dde8afe203bf2e95"}},
new CustomFormatConfigYaml {TrashIds = new[] {"240770601cc226190c367ef59aba7463"}}
new CustomFormatConfigYaml
{
TrashIds = new[]
{
"496f355514737f7d83bf7aa4d24f8169",
"240770601cc226190c367ef59aba7463"
}
},
new CustomFormatConfigYaml
{
TrashIds = new[]
{
"2f22d89048b01681dde8afe203bf2e95"
}
}
},
QualityDefinition = new QualitySizeConfigYaml
{

Loading…
Cancel
Save