diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f0920b..7504d50a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Recyclarr will now continue if `git fetch` fails for any repos, so long as there is an existing, valid clone to use. +### Fixed + +- Address regression causing `reset_unmatched_scores: false` to not be respected. + ## [5.0.0] - 2023-06-22 This release contains **BREAKING CHANGES**. See the [v5.0 Upgrade Guide][breaking5] for required diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiPersistencePhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiPersistencePhase.cs index 91f8efe6..49cb2b6c 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiPersistencePhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiPersistencePhase.cs @@ -32,7 +32,12 @@ public class QualityProfileApiPersistencePhase private void LogQualityProfileUpdates(QualityProfileTransactionData transactions) { var updatedScores = transactions.UpdatedProfiles - .Select(x => (x.UpdatedProfile.Name, x.UpdatedScores)) + .Select(x => ( + ProfileName: x.UpdatedProfile.Name, + Scores: x.UpdatedScores + .Where(y => y.Reason != FormatScoreUpdateReason.New && y.Dto.Score != y.NewScore) + .ToList())) + .Where(x => x.Scores.Any()) .ToList(); if (updatedScores.Count > 0) @@ -50,7 +55,7 @@ public class QualityProfileApiPersistencePhase _log.Information("Updated {ProfileCount} profiles and a total of {ScoreCount} scores", transactions.UpdatedProfiles.Count, - updatedScores.Sum(s => s.UpdatedScores.Count)); + updatedScores.Sum(s => s.Scores.Count)); } else { diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfilePreviewPhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfilePreviewPhase.cs index aea2dbf7..a26fd7eb 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfilePreviewPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfilePreviewPhase.cs @@ -26,7 +26,7 @@ public class QualityProfilePreviewPhase .AddColumn("[bold]New[/]") .AddColumn("[bold]Reason[/]"); - foreach (var updatedScore in updatedScores.Where(x => x.Reason != FormatScoreUpdateReason.NoChange)) + foreach (var updatedScore in updatedScores.Where(x => x.Dto.Score != x.NewScore)) { table.AddRow( updatedScore.Dto.Name, diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhase.cs index d82873ac..a8301dcd 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhase.cs @@ -71,28 +71,31 @@ public class QualityProfileTransactionPhase .FullJoin(profileDto.FormatItems, x => x.FormatId, x => x.Format, + // Exists in config, but not in service (these are unusual and should be errors) + // See `FormatScoreUpdateReason` for reason why we need this (it's preview mode) l => new UpdatedFormatScore { Dto = new ProfileFormatItemDto {Format = l.FormatId, Name = l.CfName}, NewScore = l.Score, Reason = FormatScoreUpdateReason.New }, + // Exists in service, but not in config r => new UpdatedFormatScore { Dto = r, - NewScore = 0, + NewScore = profileData.Profile.ResetUnmatchedScores ? 0 : r.Score, Reason = FormatScoreUpdateReason.Reset }, + // Exists in both service and config (l, r) => new UpdatedFormatScore { Dto = r, NewScore = l.Score, Reason = FormatScoreUpdateReason.Updated }) - .Select(x => x.Dto.Score == x.NewScore ? x with {Reason = FormatScoreUpdateReason.NoChange} : x) .ToList(); - return scoreMap.Any(x => x.Reason != FormatScoreUpdateReason.NoChange) + return scoreMap.Any(x => x.Dto.Score != x.NewScore) ? new UpdatedQualityProfile(profileDto) {UpdatedScores = scoreMap} : null; } diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedFormatScore.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedFormatScore.cs index 5106a691..f40a6f20 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedFormatScore.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedFormatScore.cs @@ -4,11 +4,6 @@ namespace Recyclarr.Cli.Pipelines.QualityProfile; public enum FormatScoreUpdateReason { - /// - /// A score who's value did not change. - /// - NoChange, - /// /// A score that is changed. /// diff --git a/src/Recyclarr.TrashLib/Config/Services/ServiceConfiguration.cs b/src/Recyclarr.TrashLib/Config/Services/ServiceConfiguration.cs index 4fe7534a..2c2c6eb5 100644 --- a/src/Recyclarr.TrashLib/Config/Services/ServiceConfiguration.cs +++ b/src/Recyclarr.TrashLib/Config/Services/ServiceConfiguration.cs @@ -47,6 +47,6 @@ public record QualityDefinitionConfig public record QualityProfileConfig { - public bool? ResetUnmatchedScores { get; init; } + public bool ResetUnmatchedScores { get; init; } public string Name { get; init; } = ""; } diff --git a/src/tests/Recyclarr.Cli.TestLibrary/NewQp.cs b/src/tests/Recyclarr.Cli.TestLibrary/NewQp.cs index f7ffd78b..e9cdbecf 100644 --- a/src/tests/Recyclarr.Cli.TestLibrary/NewQp.cs +++ b/src/tests/Recyclarr.Cli.TestLibrary/NewQp.cs @@ -11,12 +11,12 @@ public static class NewQp string profileName, params (string TrashId, int FormatId, int Score)[] scores) { - return Processed(profileName, null, scores); + return Processed(profileName, false, scores); } public static ProcessedQualityProfileData Processed( string profileName, - bool? resetUnmatchedScores, + bool resetUnmatchedScores, params (string TrashId, int FormatId, int Score)[] scores) { return Processed(profileName, resetUnmatchedScores, @@ -25,7 +25,7 @@ public static class NewQp public static ProcessedQualityProfileData Processed( string profileName, - bool? resetUnmatchedScores, + bool resetUnmatchedScores, params (string CfName, string TrashId, int FormatId, int Score)[] scores) { return new ProcessedQualityProfileData(new QualityProfileConfig diff --git a/src/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhaseTest.cs b/src/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhaseTest.cs index a567ef71..b7d1033a 100644 --- a/src/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhaseTest.cs +++ b/src/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhaseTest.cs @@ -151,7 +151,7 @@ public class QualityProfileTransactionPhaseTest } [Test, AutoMockData] - public void Reset_scores( + public void Reset_scores_with_reset_unmatched_true( QualityProfileTransactionPhase sut) { var guideData = new[] @@ -194,4 +194,48 @@ public class QualityProfileTransactionPhaseTest NewQp.UpdatedScore("quality4", 0, 500, FormatScoreUpdateReason.New) }, o => o.Excluding(x => x.Dto.Format)); } + + [Test, AutoMockData] + public void Reset_scores_with_reset_unmatched_false(QualityProfileTransactionPhase sut) + { + var guideData = new[] + { + NewQp.Processed("profile1", false, ("quality3", "id3", 3, 100), ("quality4", "id4", 4, 500)) + }; + + var serviceData = new[] + { + new QualityProfileDto + { + Name = "profile1", + FormatItems = new[] + { + new ProfileFormatItemDto + { + Name = "quality1", + Format = 1, + Score = 200 + }, + new ProfileFormatItemDto + { + Name = "quality2", + Format = 2, + Score = 300 + } + } + } + }; + + var result = sut.Execute(guideData, serviceData); + + result.UpdatedProfiles.Should() + .ContainSingle().Which.UpdatedScores.Should() + .BeEquivalentTo(new[] + { + NewQp.UpdatedScore("quality1", 200, 200, FormatScoreUpdateReason.Reset), + NewQp.UpdatedScore("quality2", 300, 300, FormatScoreUpdateReason.Reset), + NewQp.UpdatedScore("quality3", 0, 100, FormatScoreUpdateReason.New), + NewQp.UpdatedScore("quality4", 0, 500, FormatScoreUpdateReason.New) + }, o => o.Excluding(x => x.Dto.Format)); + } }