diff --git a/CHANGELOG.md b/CHANGELOG.md index 70830d7d..566abe5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,11 @@ changes you may need to make. - **BREAKING**: The app data directory on OSX has changed. It now lives at `~/Library/Application Support/recyclarr` instead of `~/.config/recyclarr`. Users will need to run `recyclarr migrate` to move the directory (or do it manually). +- **BREAKING**: Removed support for Release Profiles and Sonarr version 3. The new minimum required + version for Sonarr is v4.0.0. - CLI: Slightly improved display of version number when using `-v` option. +- CLI: Greatly improved the layout of and information in the local starter YAML configuration that + Recyclarr generates with the `recyclarr config create` command. ### Fixed diff --git a/README.md b/README.md index dd9feb14..b60c5627 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,11 @@ guides](https://trash-guides.info/) to your Sonarr/Radarr instances. ## Features -Recyclarr supports Radarr, Sonarr v3, and Sonarr v4. The following information can be synced to -these services from the TRaSH Guides. For a more detailed features list, see the [Features] page. +Recyclarr supports Radarr and Sonarr (v4 and higher only). The following information can be synced +to these services from the TRaSH Guides. For a more detailed features list, see the [Features] page. [Features]: https://recyclarr.dev/wiki/features/ -- Release Profiles, including tags - Quality Profiles, including qualities and quality groups - Custom Formats, including scores (from guide or manual) - Quality Definitions (file sizes) diff --git a/schemas/config-schema.json b/schemas/config-schema.json index 513cac61..8c27a3c5 100644 --- a/schemas/config-schema.json +++ b/schemas/config-schema.json @@ -111,9 +111,6 @@ "include": { "$ref": "config/includes.json" }, - "release_profiles": { - "$ref": "config/release-profiles.json" - }, "media_naming": { "$ref": "config/media-naming-sonarr.json" } diff --git a/schemas/config/release-profiles.json b/schemas/config/release-profiles.json deleted file mode 100644 index 03a4e674..00000000 --- a/schemas/config/release-profiles.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "https://raw.githubusercontent.com/recyclarr/recyclarr/master/schemas/config/release-profiles.json", - "type": "array", - "minItems": 1, - "items": { - "additionalProperties": false, - "required": ["trash_ids"], - "properties": { - "trash_ids": { - "$ref": "trash-ids.json" - }, - "strict_negative_scores": { - "type": "boolean", - "default": false, - "description": "Enables preferred term scores less than 0 to be instead treated as \"Must Not Contain\" (ignored) terms." - }, - "tags": { - "type": "array", - "description": "A list of one or more strings representing tags that will be applied to this release profile.", - "items": { - "type": "string" - } - }, - "filter": { - "type": "object", - "additionalProperties": false, - "description": "Defines various ways that release profile terms from the guide are synchronized with Sonarr.", - "oneOf": [ - { - "required": ["include"] - }, - { - "required": ["exclude"] - } - ], - "properties": { - "include": { - "$ref": "trash-ids.json", - "description": "A list of trash_id values representing terms (Required, Ignored, or Preferred) that should be included in the created Release Profile in Sonarr." - }, - "exclude": { - "$ref": "trash-ids.json", - "description": "A list of trash_id values representing terms (Required, Ignored, or Preferred) that should be excluded from the created Release Profile in Sonarr." - } - } - } - } - } -} diff --git a/src/Recyclarr.Cli/CompositionRoot.cs b/src/Recyclarr.Cli/CompositionRoot.cs index 59a8a01b..df3cbd28 100644 --- a/src/Recyclarr.Cli/CompositionRoot.cs +++ b/src/Recyclarr.Cli/CompositionRoot.cs @@ -13,8 +13,6 @@ using Recyclarr.Cli.Pipelines.Generic; using Recyclarr.Cli.Pipelines.MediaNaming; using Recyclarr.Cli.Pipelines.QualityProfile; using Recyclarr.Cli.Pipelines.QualitySize; -using Recyclarr.Cli.Pipelines.ReleaseProfile; -using Recyclarr.Cli.Pipelines.Tags; using Recyclarr.Cli.Processors; using Recyclarr.Common; using Recyclarr.Compatibility; @@ -69,22 +67,18 @@ public static class CompositionRoot private static void PipelineRegistrations(ContainerBuilder builder) { - builder.RegisterModule(); builder.RegisterModule(); builder.RegisterModule(); builder.RegisterModule(); - builder.RegisterModule(); builder.RegisterModule(); builder.RegisterGeneric(typeof(GenericPipelinePhases<>)); builder.RegisterTypes( // ORDER HERE IS IMPORTANT! // There are indirect dependencies between pipelines. - typeof(GenericSyncPipeline), typeof(GenericSyncPipeline), typeof(GenericSyncPipeline), typeof(GenericSyncPipeline), - typeof(GenericSyncPipeline), typeof(GenericSyncPipeline)) .As() .OrderByRegistration(); diff --git a/src/Recyclarr.Cli/Console/CliSetup.cs b/src/Recyclarr.Cli/Console/CliSetup.cs index 2207dadf..c8d88aa9 100644 --- a/src/Recyclarr.Cli/Console/CliSetup.cs +++ b/src/Recyclarr.Cli/Console/CliSetup.cs @@ -18,7 +18,6 @@ public static class CliSetup { list.SetDescription("List information from the guide"); list.AddCommand("custom-formats"); - list.AddCommand("release-profiles"); list.AddCommand("qualities"); list.AddCommand("naming"); }); diff --git a/src/Recyclarr.Cli/Console/Commands/ListReleaseProfilesCommand.cs b/src/Recyclarr.Cli/Console/Commands/ListReleaseProfilesCommand.cs deleted file mode 100644 index 9d751c5f..00000000 --- a/src/Recyclarr.Cli/Console/Commands/ListReleaseProfilesCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using JetBrains.Annotations; -using Recyclarr.Cli.Pipelines.ReleaseProfile; -using Recyclarr.Repo; -using Spectre.Console.Cli; - -#pragma warning disable CS8765 - -namespace Recyclarr.Cli.Console.Commands; - -[UsedImplicitly] -[Description("List Sonarr release profiles in the guide for a particular service.")] -public class ListReleaseProfilesCommand(ILogger log, ReleaseProfileDataLister lister, IMultiRepoUpdater repoUpdater) - : AsyncCommand -{ - [UsedImplicitly] - [SuppressMessage("Design", "CA1034:Nested types should not be visible")] - public class CliSettings : BaseCommandSettings - { - [CommandOption("--terms")] - [Description( - "For the given Release Profile Trash ID, list terms in it that can be filtered in YAML format. " + - "Note that not every release profile has terms that may be filtered.")] - [UsedImplicitly(ImplicitUseKindFlags.Assign)] - public string? ListTerms { get; init; } - } - - public override async Task ExecuteAsync(CommandContext context, CliSettings settings) - { - await repoUpdater.UpdateAllRepositories(settings.CancellationToken); - - try - { - if (settings.ListTerms is not null) - { - // Ignore nullability of ListTerms since the Settings.Validate() method will check for null/empty. - lister.ListTerms(settings.ListTerms!); - } - else - { - lister.ListReleaseProfiles(); - } - } - catch (ArgumentException e) - { - log.Error(e, "Error"); - return 1; - } - - return 0; - } -} diff --git a/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/Config/NamingFormatLookup.cs b/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/Config/NamingFormatLookup.cs index e8df30d1..b1dee59c 100644 --- a/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/Config/NamingFormatLookup.cs +++ b/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/Config/NamingFormatLookup.cs @@ -9,15 +9,6 @@ public class NamingFormatLookup IReadOnlyDictionary guideFormats, string? configFormatKey, string errorDescription) - { - return ObtainFormat(guideFormats, configFormatKey, null, errorDescription); - } - - public string? ObtainFormat( - IReadOnlyDictionary guideFormats, - string? configFormatKey, - string? keySuffix, - string errorDescription) { if (configFormatKey is null) { @@ -29,11 +20,6 @@ public class NamingFormatLookup var lowerKey = configFormatKey.ToLowerInvariant(); var keys = new List {lowerKey}; - if (keySuffix is not null) - { - // Put the more specific key first - keys.Insert(0, lowerKey + keySuffix); - } foreach (var k in keys) { diff --git a/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/Config/SonarrMediaNamingConfigPhase.cs b/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/Config/SonarrMediaNamingConfigPhase.cs index 189b8419..0b1e37c1 100644 --- a/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/Config/SonarrMediaNamingConfigPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/Config/SonarrMediaNamingConfigPhase.cs @@ -1,43 +1,36 @@ -using Recyclarr.Compatibility.Sonarr; using Recyclarr.Config.Models; using Recyclarr.ServarrApi.MediaNaming; using Recyclarr.TrashGuide.MediaNaming; namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config; -public class SonarrMediaNamingConfigPhase(ISonarrCapabilityFetcher sonarrCapabilities) - : ServiceBasedMediaNamingConfigPhase +public class SonarrMediaNamingConfigPhase : ServiceBasedMediaNamingConfigPhase { - protected override async Task ProcessNaming( + protected override Task ProcessNaming( SonarrConfiguration config, IMediaNamingGuideService guide, NamingFormatLookup lookup) { var guideData = guide.GetSonarrNamingData(); var configData = config.MediaNaming; - var capabilities = await sonarrCapabilities.GetCapabilities(config); - var keySuffix = capabilities.SupportsCustomFormats ? ":4" : ":3"; - return new SonarrMediaNamingDto + return Task.FromResult(new SonarrMediaNamingDto { SeasonFolderFormat = lookup.ObtainFormat(guideData.Season, configData.Season, "Season Folder Format"), SeriesFolderFormat = lookup.ObtainFormat(guideData.Series, configData.Series, "Series Folder Format"), StandardEpisodeFormat = lookup.ObtainFormat( guideData.Episodes.Standard, configData.Episodes?.Standard, - keySuffix, "Standard Episode Format"), DailyEpisodeFormat = lookup.ObtainFormat( guideData.Episodes.Daily, configData.Episodes?.Daily, - keySuffix, "Daily Episode Format"), AnimeEpisodeFormat = lookup.ObtainFormat( guideData.Episodes.Anime, configData.Episodes?.Anime, - keySuffix, "Anime Episode Format"), RenameEpisodes = configData.Episodes?.Rename - }; + }); } } diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IReleaseProfileFilter.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IReleaseProfileFilter.cs deleted file mode 100644 index be8bbe79..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IReleaseProfileFilter.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; - -public interface IReleaseProfileFilter -{ - ReleaseProfileData Transform(ReleaseProfileData profile, ReleaseProfileConfig config); -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IReleaseProfileFilterPipeline.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IReleaseProfileFilterPipeline.cs deleted file mode 100644 index 849e67cc..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IReleaseProfileFilterPipeline.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; - -public interface IReleaseProfileFilterPipeline -{ - ReleaseProfileData Process(ReleaseProfileData profile, ReleaseProfileConfig config); -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IncludeExcludeFilter.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IncludeExcludeFilter.cs deleted file mode 100644 index d9fa0fd9..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/IncludeExcludeFilter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; - -public class IncludeExcludeFilter(ILogger log) : IReleaseProfileFilter -{ - private readonly ReleaseProfileDataFilterer _filterer = new(log); - - public ReleaseProfileData Transform(ReleaseProfileData profile, ReleaseProfileConfig config) - { - if (config.Filter == null) - { - return profile; - } - - log.Debug("This profile will be filtered"); - var newProfile = _filterer.FilterProfile(profile, config.Filter); - return newProfile ?? profile; - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/ReleaseProfileDataFilterer.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/ReleaseProfileDataFilterer.cs deleted file mode 100644 index c109b49f..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/ReleaseProfileDataFilterer.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Collections.ObjectModel; -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; - -public class ReleaseProfileDataFilterer(ILogger log) -{ - private readonly ReleaseProfileDataValidationFilterer _validator = new(log); - - public ReadOnlyCollection ExcludeTerms( - IEnumerable terms, - IEnumerable excludeFilter) - { - var result = terms.Where(x => !excludeFilter.Contains(x.TrashId, StringComparer.InvariantCultureIgnoreCase)); - return _validator.FilterTerms(result).ToList().AsReadOnly(); - } - - public ReadOnlyCollection ExcludeTerms( - IEnumerable terms, - IReadOnlyCollection excludeFilter) - { - var result = terms - .Select(x => new PreferredTermData - { - Score = x.Score, - Terms = ExcludeTerms(x.Terms, excludeFilter) - }); - - return _validator.FilterTerms(result).ToList().AsReadOnly(); - } - - public ReadOnlyCollection IncludeTerms( - IEnumerable terms, - IEnumerable includeFilter) - { - var result = terms.Where(x => includeFilter.Contains(x.TrashId, StringComparer.InvariantCultureIgnoreCase)); - return _validator.FilterTerms(result).ToList().AsReadOnly(); - } - - public ReadOnlyCollection IncludeTerms( - IEnumerable terms, - IReadOnlyCollection includeFilter) - { - var result = terms - .Select(x => new PreferredTermData - { - Score = x.Score, - Terms = IncludeTerms(x.Terms, includeFilter) - }); - - return _validator.FilterTerms(result).ToList().AsReadOnly(); - } - - public ReleaseProfileData? FilterProfile( - ReleaseProfileData selectedProfile, - SonarrProfileFilterConfig profileFilter) - { - if (profileFilter.Include.Count != 0) - { - log.Debug("Using inclusion filter"); - return selectedProfile with - { - Required = IncludeTerms(selectedProfile.Required, profileFilter.Include), - Ignored = IncludeTerms(selectedProfile.Ignored, profileFilter.Include), - Preferred = IncludeTerms(selectedProfile.Preferred, profileFilter.Include) - }; - } - - if (profileFilter.Exclude.Count != 0) - { - log.Debug("Using exclusion filter"); - return selectedProfile with - { - Required = ExcludeTerms(selectedProfile.Required, profileFilter.Exclude), - Ignored = ExcludeTerms(selectedProfile.Ignored, profileFilter.Exclude), - Preferred = ExcludeTerms(selectedProfile.Preferred, profileFilter.Exclude) - }; - } - - log.Debug("Filter property present but is empty"); - return null; - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/ReleaseProfileFilterPipeline.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/ReleaseProfileFilterPipeline.cs deleted file mode 100644 index be818aa6..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/ReleaseProfileFilterPipeline.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; - -public class ReleaseProfileFilterPipeline(IOrderedEnumerable filters) - : IReleaseProfileFilterPipeline -{ - public ReleaseProfileData Process(ReleaseProfileData profile, ReleaseProfileConfig config) - { - return filters.Aggregate(profile, (current, filter) => filter.Transform(current, config)); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/StrictNegativeScoresFilter.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/StrictNegativeScoresFilter.cs deleted file mode 100644 index 8d23790d..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Filters/StrictNegativeScoresFilter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; - -public class StrictNegativeScoresFilter(ILogger log) : IReleaseProfileFilter -{ - public ReleaseProfileData Transform(ReleaseProfileData profile, ReleaseProfileConfig config) - { - if (!config.StrictNegativeScores) - { - return profile; - } - - log.Debug("Negative scores will be strictly ignored"); - var splitPreferred = profile.Preferred.ToLookup(x => x.Score < 0); - - return profile with - { - Ignored = profile.Ignored.Concat(splitPreferred[true].SelectMany(x => x.Terms)).ToList(), - Preferred = splitPreferred[false].ToList() - }; - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Models/ReleaseProfileTransactionData.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Models/ReleaseProfileTransactionData.cs deleted file mode 100644 index f4d1b0cb..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/Models/ReleaseProfileTransactionData.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Recyclarr.ServarrApi.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Models; - -public record ReleaseProfileTransactionData( - IReadOnlyCollection UpdatedProfiles, - IReadOnlyCollection CreatedProfiles, - IReadOnlyCollection DeletedProfiles -); diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileApiFetchPhase.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileApiFetchPhase.cs deleted file mode 100644 index b3fecc98..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileApiFetchPhase.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; - -public class ReleaseProfileApiFetchPhase(IReleaseProfileApiService rpService) - : IApiFetchPipelinePhase -{ - public async Task Execute(ReleaseProfilePipelineContext context, IServiceConfiguration config) - { - context.ApiFetchOutput = await rpService.GetReleaseProfiles(config); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileApiPersistencePhase.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileApiPersistencePhase.cs deleted file mode 100644 index d334ef23..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileApiPersistencePhase.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; - -public class ReleaseProfileApiPersistencePhase(IReleaseProfileApiService api) - : IApiPersistencePipelinePhase -{ - public async Task Execute(ReleaseProfilePipelineContext context, IServiceConfiguration config) - { - var transactions = context.TransactionOutput; - - foreach (var profile in transactions.UpdatedProfiles) - { - await api.UpdateReleaseProfile(config, profile); - } - - foreach (var profile in transactions.CreatedProfiles) - { - await api.CreateReleaseProfile(config, profile); - } - - foreach (var profile in transactions.DeletedProfiles) - { - await api.DeleteReleaseProfile(config, profile.Id); - } - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileConfigPhase.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileConfigPhase.cs deleted file mode 100644 index d5ba0e2e..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileConfigPhase.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; -using Recyclarr.Common.Extensions; -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; - -public record ProcessedReleaseProfileData( - ReleaseProfileData Profile, - IReadOnlyCollection Tags -); - -public class ReleaseProfileConfigPhase( - ILogger log, - IReleaseProfileGuideService guide, - IReleaseProfileFilterPipeline filters) - : IConfigPipelinePhase -{ - public Task Execute(ReleaseProfilePipelineContext context, IServiceConfiguration config) - { - var releaseProfiles = ((SonarrConfiguration) config).ReleaseProfiles; - if (!releaseProfiles.Any()) - { - log.Debug("{Instance} has no release profiles", config.InstanceName); - return Task.CompletedTask; - } - - var profilesFromGuide = guide.GetReleaseProfileData(); - var filteredProfiles = new List(); - - var configProfiles = releaseProfiles.SelectMany(x => x.TrashIds.Select(y => (TrashId: y, Config: x))); - foreach (var (trashId, configProfile) in configProfiles) - { - // For each release profile specified in our YAML config, find the matching profile in the guide. - var selectedProfile = profilesFromGuide.FirstOrDefault(x => x.TrashId.EqualsIgnoreCase(trashId)); - if (selectedProfile is null) - { - log.Warning("A release profile with Trash ID {TrashId} does not exist", trashId); - continue; - } - - log.Debug("Found Release Profile: {ProfileName} ({TrashId})", selectedProfile.Name, - selectedProfile.TrashId); - - selectedProfile = filters.Process(selectedProfile, configProfile); - filteredProfiles.Add(new ProcessedReleaseProfileData(selectedProfile, configProfile.Tags)); - } - - context.ConfigOutput = filteredProfiles; - return Task.CompletedTask; - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileLogPhase.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileLogPhase.cs deleted file mode 100644 index 36637013..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileLogPhase.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; - -public class ReleaseProfileLogPhase(ILogger log) : ILogPipelinePhase -{ - public bool LogConfigPhaseAndExitIfNeeded(ReleaseProfilePipelineContext context) - { - if (context.ConfigOutput is {Count: > 0}) - { - return false; - } - - log.Debug("No Release Profiles to process"); - return true; - } - - public void LogTransactionNotices(ReleaseProfilePipelineContext context) - { - } - - public void LogPersistenceResults(ReleaseProfilePipelineContext context) - { - var transactions = context.TransactionOutput; - var somethingChanged = false; - - if (transactions.UpdatedProfiles.Count != 0) - { - log.Information("Update existing profiles: {ProfileNames}", - transactions.UpdatedProfiles.Select(x => x.Name)); - somethingChanged = true; - } - - if (transactions.CreatedProfiles.Count != 0) - { - log.Information("Create new profiles: {ProfileNames}", transactions.CreatedProfiles.Select(x => x.Name)); - somethingChanged = true; - } - - if (transactions.DeletedProfiles.Count != 0) - { - log.Information("Deleting old release profiles: {ProfileNames}", - transactions.DeletedProfiles.Select(x => x.Name)); - somethingChanged = true; - } - - if (!somethingChanged) - { - log.Information("All Release Profiles are up to date!"); - } - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfilePreviewPhase.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfilePreviewPhase.cs deleted file mode 100644 index fa1981d3..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfilePreviewPhase.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.ServarrApi.ReleaseProfile; -using Spectre.Console; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; - -public class ReleaseProfilePreviewPhase(IAnsiConsole console) : IPreviewPipelinePhase -{ - public void Execute(ReleaseProfilePipelineContext context) - { - var profiles = context.TransactionOutput; - - var tree = new Tree("Release Profiles [red](Preview)[/]"); - - PrintCategoryOfChanges("Created Profiles", tree, profiles.CreatedProfiles); - PrintCategoryOfChanges("Updated Profiles", tree, profiles.UpdatedProfiles); - - console.WriteLine(); - console.Write(tree); - } - - private void PrintCategoryOfChanges(string nodeTitle, Tree tree, IEnumerable profiles) - { - var treeNode = tree.AddNode($"[green]{nodeTitle}[/]"); - foreach (var profile in profiles) - { - PrintTermsAndScores(treeNode, profile); - } - } - - private void PrintTermsAndScores(TreeNode tree, SonarrReleaseProfile profile) - { - var rpNode = tree.AddNode($"[yellow]{Markup.Escape(profile.Name)}[/]"); - - var incPreferred = profile.IncludePreferredWhenRenaming ? "[green]YES[/]" : "[red]NO[/]"; - rpNode.AddNode($"Include Preferred when Renaming? {incPreferred}"); - - PrintTerms(rpNode, "Must Contain", profile.Required); - PrintTerms(rpNode, "Must Not Contain", profile.Ignored); - PrintPreferredTerms(rpNode, "Preferred", profile.Preferred); - - console.WriteLine(""); - } - - private static void PrintTerms(TreeNode tree, string title, IReadOnlyCollection terms) - { - if (terms.Count == 0) - { - return; - } - - var table = new Table() - .AddColumn("[bold]Term[/]"); - - foreach (var term in terms) - { - table.AddRow(Markup.Escape(term)); - } - - tree.AddNode(title) - .AddNode(table); - } - - private static void PrintPreferredTerms( - TreeNode tree, - string title, - IReadOnlyCollection preferredTerms) - { - if (preferredTerms.Count <= 0) - { - return; - } - - var table = new Table() - .AddColumn("[bold]Score[/]") - .AddColumn("[bold]Term[/]"); - - foreach (var term in preferredTerms) - { - table.AddRow(term.Score.ToString(), Markup.Escape(term.Term)); - } - - tree.AddNode(title) - .AddNode(table); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileTransactionPhase.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileTransactionPhase.cs deleted file mode 100644 index e6280303..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/PipelinePhases/ReleaseProfileTransactionPhase.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Cli.Pipelines.ReleaseProfile.Models; -using Recyclarr.Cli.Pipelines.Tags; -using Recyclarr.Common.Extensions; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; - -public class ReleaseProfileTransactionPhase(ServiceTagCache tagCache) - : ITransactionPipelinePhase -{ - public void Execute(ReleaseProfilePipelineContext context, IServiceConfiguration config) - { - var created = new List(); - var updated = new List(); - - foreach (var configProfile in context.ConfigOutput) - { - var title = $"[Trash] {configProfile.Profile.Name}"; - var matchingServiceProfile = context.ApiFetchOutput.FirstOrDefault(x => x.Name.EqualsIgnoreCase(title)); - if (matchingServiceProfile is not null) - { - SetupProfileRequestObject(matchingServiceProfile, configProfile); - updated.Add(matchingServiceProfile); - } - else - { - var profileToUpdate = new SonarrReleaseProfile {Name = title, Enabled = true}; - SetupProfileRequestObject(profileToUpdate, configProfile); - created.Add(profileToUpdate); - } - } - - var deleted = DeleteOldManagedProfiles(context.ApiFetchOutput, context.ConfigOutput.AsReadOnly()); - context.TransactionOutput = new ReleaseProfileTransactionData(updated, created, deleted); - } - - private static List DeleteOldManagedProfiles( - IList serviceData, - IReadOnlyList configProfiles) - { - var profiles = configProfiles.Select(x => x.Profile).ToList(); - return serviceData - .Where(sonarrProfile => - { - return sonarrProfile.Name.StartsWithIgnoreCase("[Trash]") && - !profiles.Exists(profile => sonarrProfile.Name.EndsWithIgnoreCase(profile.Name)); - }) - .ToList(); - } - - private void SetupProfileRequestObject(SonarrReleaseProfile profileToUpdate, ProcessedReleaseProfileData profile) - { - profileToUpdate.Preferred = profile.Profile.Preferred - .SelectMany(x => x.Terms.Select(termData => new SonarrPreferredTerm(x.Score, termData.Term))) - .ToList(); - - profileToUpdate.Ignored = profile.Profile.Ignored.Select(x => x.Term).ToList(); - profileToUpdate.Required = profile.Profile.Required.Select(x => x.Term).ToList(); - profileToUpdate.IncludePreferredWhenRenaming = profile.Profile.IncludePreferredWhenRenaming; - profileToUpdate.Tags = profile.Tags - .Select(tagCache.GetTagIdByName) - .NotNull() - .ToList(); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfileAutofacModule.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfileAutofacModule.cs deleted file mode 100644 index 313feea5..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfileAutofacModule.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Autofac; -using Autofac.Extras.Ordering; -using Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; -using Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; -using Recyclarr.ServarrApi.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile; - -public class ReleaseProfileAutofacModule : Module -{ - protected override void Load(ContainerBuilder builder) - { - base.Load(builder); - - builder.RegisterType().As(); - builder.RegisterType().As(); - builder.RegisterType(); - - // Release Profile Filters (ORDER MATTERS!) - builder.RegisterTypes( - typeof(IncludeExcludeFilter), - typeof(StrictNegativeScoresFilter)) - .As() - .OrderByRegistration(); - - builder.RegisterTypes( - typeof(ReleaseProfileConfigPhase), - typeof(ReleaseProfilePreviewPhase), - typeof(ReleaseProfileApiFetchPhase), - typeof(ReleaseProfileTransactionPhase), - typeof(ReleaseProfileApiPersistencePhase), - typeof(ReleaseProfileLogPhase)) - .AsImplementedInterfaces(); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfileDataLister.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfileDataLister.cs deleted file mode 100644 index 31816b83..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfileDataLister.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Text; -using Recyclarr.Common.Extensions; -using Recyclarr.TrashGuide.ReleaseProfile; -using Spectre.Console; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile; - -public class ReleaseProfileDataLister(IAnsiConsole console, IReleaseProfileGuideService guide) -{ - public void ListReleaseProfiles() - { - console.WriteLine("\nList of Release Profiles in the TRaSH Guides:\n"); - - var profilesFromGuide = guide.GetReleaseProfileData(); - foreach (var profile in profilesFromGuide) - { - console.WriteLine($" - {profile.TrashId} # {profile.Name}"); - } - - console.WriteLine( - "\nThe above Release Profiles are in YAML format and ready to be copied & pasted under the `trash_ids:` property."); - } - - private static bool HasIdentifiableTerms(ReleaseProfileData profile) - { - static bool HasTrashIds(IEnumerable terms) - { - return terms.Any(x => !string.IsNullOrEmpty(x.TrashId)); - } - - return - HasTrashIds(profile.Ignored) || - HasTrashIds(profile.Required) || - HasTrashIds(profile.Preferred.SelectMany(x => x.Terms)); - } - - public void ListTerms(string releaseProfileId) - { - var profile = guide.GetReleaseProfileData() - .FirstOrDefault(x => x.TrashId.EqualsIgnoreCase(releaseProfileId)); - - if (profile is null) - { - throw new ArgumentException("No release profile found with that Trash ID"); - } - - if (!HasIdentifiableTerms(profile)) - { - throw new ArgumentException( - "This release profile has no terms that can be filtered " + - "(terms must have Trash IDs assigned in order to be filtered)"); - } - - console.WriteLine(); - console.WriteLine($"List of Terms for the '{profile.Name}' Release Profile that may be filtered:\n"); - - PrintTerms(profile.Required, "Required"); - PrintTerms(profile.Ignored, "Ignored"); - PrintTerms(profile.Preferred.SelectMany(x => x.Terms), "Preferred"); - - console.WriteLine( - "The above Term Filters are in YAML format and ready to be copied & pasted under the `include:` or `exclude:` filter properties."); - } - - private void PrintTerms(IEnumerable terms, string category) - { - var filteredTerms = terms.Where(x => x.TrashId.Length != 0).ToList(); - if (filteredTerms.Count == 0) - { - return; - } - - console.WriteLine($"{category} Terms:\n"); - foreach (var term in filteredTerms) - { - var line = new StringBuilder($" - {term.TrashId}"); - if (term.Name.Length != 0) - { - line.Append($" # {term.Name}"); - } - - console.WriteLine(line.ToString()); - } - - console.WriteLine(); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfilePipelineContext.cs b/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfilePipelineContext.cs deleted file mode 100644 index 2354806a..00000000 --- a/src/Recyclarr.Cli/Pipelines/ReleaseProfile/ReleaseProfilePipelineContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Cli.Pipelines.ReleaseProfile.Models; -using Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases; -using Recyclarr.Common; -using Recyclarr.ServarrApi.ReleaseProfile; - -namespace Recyclarr.Cli.Pipelines.ReleaseProfile; - -[SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = - "Context objects are similar to DTOs; for usability we want to assign not append")] -public class ReleaseProfilePipelineContext : IPipelineContext -{ - public string PipelineDescription => "Release Profile Pipeline"; - public IReadOnlyCollection SupportedServiceTypes { get; } = new[] - { - SupportedServices.Sonarr - }; - - public IList ConfigOutput { get; set; } = default!; - public IList ApiFetchOutput { get; set; } = default!; - public ReleaseProfileTransactionData TransactionOutput { get; set; } = default!; -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagApiFetchPhase.cs b/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagApiFetchPhase.cs deleted file mode 100644 index 83e9e888..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagApiFetchPhase.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.Tag; - -namespace Recyclarr.Cli.Pipelines.Tags.PipelinePhases; - -public class TagApiFetchPhase(ISonarrTagApiService api, ServiceTagCache cache) - : IApiFetchPipelinePhase -{ - public async Task Execute(TagPipelineContext context, IServiceConfiguration config) - { - var tags = await api.GetTags(config); - cache.AddTags(tags); - context.ApiFetchOutput = tags; - } -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagApiPersistencePhase.cs b/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagApiPersistencePhase.cs deleted file mode 100644 index 840ce84b..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagApiPersistencePhase.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.Tag; - -namespace Recyclarr.Cli.Pipelines.Tags.PipelinePhases; - -public class TagApiPersistencePhase(ILogger log, ServiceTagCache cache, ISonarrTagApiService api) - : IApiPersistencePipelinePhase -{ - public async Task Execute(TagPipelineContext context, IServiceConfiguration config) - { - var createdTags = new List(); - foreach (var tag in context.TransactionOutput) - { - log.Debug("Creating Tag: {Tag}", tag); - createdTags.Add(await api.CreateTag(config, tag)); - } - - cache.AddTags(createdTags); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagConfigPhase.cs b/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagConfigPhase.cs deleted file mode 100644 index e2456196..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagConfigPhase.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Config.Models; - -namespace Recyclarr.Cli.Pipelines.Tags.PipelinePhases; - -public class TagConfigPhase : IConfigPipelinePhase -{ - public Task Execute(TagPipelineContext context, IServiceConfiguration config) - { - context.ConfigOutput = ((SonarrConfiguration) config).ReleaseProfiles - .SelectMany(x => x.Tags) - .Distinct() - .ToList(); - - return Task.CompletedTask; - } -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagLogPhase.cs b/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagLogPhase.cs deleted file mode 100644 index 819c9a2f..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagLogPhase.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; - -namespace Recyclarr.Cli.Pipelines.Tags.PipelinePhases; - -public class TagLogPhase(ILogger log) : ILogPipelinePhase -{ - public bool LogConfigPhaseAndExitIfNeeded(TagPipelineContext context) - { - if (!context.ConfigOutput.Any()) - { - log.Debug("No tags to process"); - return true; - } - - return false; - } - - public void LogTransactionNotices(TagPipelineContext context) - { - } - - public void LogPersistenceResults(TagPipelineContext context) - { - if (context.TransactionOutput.Any()) - { - log.Information("Created {Count} Tags: {Tags}", - context.TransactionOutput.Count, - context.TransactionOutput); - } - else - { - log.Information("All tags are up to date!"); - } - } -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagPreviewPhase.cs b/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagPreviewPhase.cs deleted file mode 100644 index a9543e52..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagPreviewPhase.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Castle.Core.Internal; -using Recyclarr.Cli.Pipelines.Generic; -using Spectre.Console; - -namespace Recyclarr.Cli.Pipelines.Tags.PipelinePhases; - -public class TagPreviewPhase(IAnsiConsole console) : IPreviewPipelinePhase -{ - public void Execute(TagPipelineContext context) - { - var tagsToCreate = context.TransactionOutput; - - if (tagsToCreate.IsNullOrEmpty()) - { - console.WriteLine(); - console.MarkupLine("[green]No tags to create[/]"); - console.WriteLine(); - return; - } - - var table = new Table {Border = TableBorder.Simple}; - table.AddColumn("[olive]Tags To Create[/]"); - - foreach (var tag in tagsToCreate) - { - table.AddRow(tag); - } - - console.Write(table); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagTransactionPhase.cs b/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagTransactionPhase.cs deleted file mode 100644 index e4304b59..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/PipelinePhases/TagTransactionPhase.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Common.Extensions; -using Recyclarr.Config.Models; - -namespace Recyclarr.Cli.Pipelines.Tags.PipelinePhases; - -public class TagTransactionPhase : ITransactionPipelinePhase -{ - public void Execute(TagPipelineContext context, IServiceConfiguration config) - { - // List of tags in config that do not already exist in the service. The goal is to figure out which tags need to - // be created. - context.TransactionOutput = context.ConfigOutput - .Where(ct => context.ApiFetchOutput.All(st => !st.Label.EqualsIgnoreCase(ct))) - .ToList(); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/ServiceTagCache.cs b/src/Recyclarr.Cli/Pipelines/Tags/ServiceTagCache.cs deleted file mode 100644 index 10a0a0e9..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/ServiceTagCache.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Recyclarr.Common.Extensions; -using Recyclarr.ServarrApi.Tag; - -namespace Recyclarr.Cli.Pipelines.Tags; - -public class ServiceTagCache : IPipelineCache -{ - private readonly HashSet _serviceTags = new(); - - public IEnumerable Tags => _serviceTags; - - public void AddTags(IEnumerable tags) - { - _serviceTags.AddRange(tags); - } - - public int? GetTagIdByName(string name) - { - var foundTag = _serviceTags.FirstOrDefault(x => x.Label.EqualsIgnoreCase(name)); - return foundTag?.Id; - } - - public void Clear() - { - _serviceTags.Clear(); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/TagPipelineContext.cs b/src/Recyclarr.Cli/Pipelines/Tags/TagPipelineContext.cs deleted file mode 100644 index 4ec50d47..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/TagPipelineContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Recyclarr.Cli.Pipelines.Generic; -using Recyclarr.Common; -using Recyclarr.ServarrApi.Tag; - -namespace Recyclarr.Cli.Pipelines.Tags; - -[SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = - "Context objects are similar to DTOs; for usability we want to assign not append")] -public class TagPipelineContext : IPipelineContext -{ - public string PipelineDescription => "Tag Pipeline"; - public IReadOnlyCollection SupportedServiceTypes { get; } = new[] - { - SupportedServices.Sonarr - }; - - public IList ConfigOutput { get; set; } = default!; - public IList ApiFetchOutput { get; set; } = default!; - public IList TransactionOutput { get; set; } = default!; -} diff --git a/src/Recyclarr.Cli/Pipelines/Tags/TagsAutofacModule.cs b/src/Recyclarr.Cli/Pipelines/Tags/TagsAutofacModule.cs deleted file mode 100644 index aa4d22a6..00000000 --- a/src/Recyclarr.Cli/Pipelines/Tags/TagsAutofacModule.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Autofac; -using Recyclarr.Cli.Pipelines.Tags.PipelinePhases; - -namespace Recyclarr.Cli.Pipelines.Tags; - -public class TagsAutofacModule : Module -{ - protected override void Load(ContainerBuilder builder) - { - base.Load(builder); - - builder.RegisterType().As() - .AsSelf() - .InstancePerLifetimeScope(); - - builder.RegisterTypes( - typeof(TagConfigPhase), - typeof(TagPreviewPhase), - typeof(TagApiFetchPhase), - typeof(TagTransactionPhase), - typeof(TagApiPersistencePhase), - typeof(TagLogPhase)) - .AsImplementedInterfaces(); - } -} diff --git a/src/Recyclarr.Cli/Processors/Delete/DeleteCustomFormatsProcessor.cs b/src/Recyclarr.Cli/Processors/Delete/DeleteCustomFormatsProcessor.cs index 7daf1b99..b7ba5d51 100644 --- a/src/Recyclarr.Cli/Processors/Delete/DeleteCustomFormatsProcessor.cs +++ b/src/Recyclarr.Cli/Processors/Delete/DeleteCustomFormatsProcessor.cs @@ -1,8 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Recyclarr.Cli.Console; using Recyclarr.Cli.Console.Settings; -using Recyclarr.Compatibility; -using Recyclarr.Compatibility.Sonarr; using Recyclarr.Config; using Recyclarr.Config.Models; using Recyclarr.ServarrApi.CustomFormat; @@ -15,16 +13,13 @@ public class DeleteCustomFormatsProcessor( ILogger log, IAnsiConsole console, ICustomFormatApiService api, - IConfigurationRegistry configRegistry, - ISonarrCapabilityFetcher sonarCapabilities) + IConfigurationRegistry configRegistry) : IDeleteCustomFormatsProcessor { public async Task Process(IDeleteCustomFormatSettings settings) { var config = GetTargetConfig(settings); - await CheckCustomFormatSupport(config); - var cfs = await ObtainCustomFormats(config); if (!settings.All) @@ -61,18 +56,6 @@ public class DeleteCustomFormatsProcessor( await DeleteCustomFormats(cfs, config); } - private async Task CheckCustomFormatSupport(IServiceConfiguration config) - { - if (config is SonarrConfiguration) - { - var capabilities = await sonarCapabilities.GetCapabilities(config); - if (!capabilities.SupportsCustomFormats) - { - throw new ServiceIncompatibilityException("Custom formats are not supported in Sonarr v3"); - } - } - } - [SuppressMessage("Design", "CA1031:Do not catch general exception types")] private async Task DeleteCustomFormats(ICollection cfs, IServiceConfiguration config) { diff --git a/src/Recyclarr.Cli/config-template.yml b/src/Recyclarr.Cli/config-template.yml index ad6a26f6..d17d97e1 100644 --- a/src/Recyclarr.Cli/config-template.yml +++ b/src/Recyclarr.Cli/config-template.yml @@ -1,75 +1,41 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/recyclarr/recyclarr/master/schemas/config-schema.json -# A starter config to use with Recyclarr. Most values are set to "reasonable defaults". Update the -# values below as needed for your instance. You will be required to update the API Key and URL for -# each instance you want to use. +# An empty starter config to use with Recyclarr. Update the values below as needed for your +# instance. You will be required to update the `api_key` and `base_url` for each instance you want +# to use. # -# Many optional settings have been omitted to keep this template simple. Note that there's no "one -# size fits all" configuration. Please refer to the guide to understand how to build the appropriate -# configuration based on your hardware setup and capabilities. +# If you'd rather use pre-built configuration instead of building your own from scratch, see these +# pages: +# - Config Templates: https://recyclarr.dev/wiki/guide-configs/ +# - CLI Command: http://recyclarr.dev/wiki/cli/config/list/templates/ # -# For any lines that mention uncommenting YAML, you simply need to remove the leading hash (`#`). -# The YAML comments will already be at the appropriate indentation. +# This file WILL NOT WORK as it currently is. You need to read the documentation and build this +# configuration from scratch. Note that there's no "one size fits all" configuration. Please refer +# to the TRaSH Guides to understand how to build the appropriate configuration based on your +# hardware setup and capabilities. # # For more details on the configuration, see the Configuration Reference on the wiki here: # https://recyclarr.dev/wiki/yaml/config-reference/ +# +# Want a more flexible file layout? +# See: https://recyclarr.dev/wiki/file-structure/ -# Configuration specific to Sonarr +# Configuration specific to Sonarr. For Radarr, the layout is the same. +# See: http://recyclarr.dev/wiki/yaml/config-reference/basic/ sonarr: series: # Set the URL/API Key to your actual instance base_url: http://localhost:8989 api_key: YOUR_KEY_HERE - # Quality definitions from the guide to sync to Sonarr. Choices: series, anime - quality_definition: - type: series - - # Release profiles from the guide to sync to Sonarr v3 (Sonarr v4 does not use this!) - # Use `recyclarr list release-profiles` for values you can put here. - # https://trash-guides.info/Sonarr/Sonarr-Release-Profile-RegEx/ - release_profiles: - # Series - - trash_ids: - - EBC725268D687D588A20CBC5F97E538B # Low Quality Groups - - 1B018E0C53EC825085DD911102E2CA36 # Release Sources (Streaming Service) - - 71899E6C303A07AF0E4746EFF9873532 # P2P Groups + Repack/Proper - # Anime (Uncomment below if you want it) - #- trash_ids: - # - d428eda85af1df8904b4bbe4fc2f537c # Anime - First release profile - # - 6cd9e10bb5bb4c63d2d7cd3279924c7b # Anime - Second release profile - -# Configuration specific to Radarr. -radarr: - movies: - # Set the URL/API Key to your actual instance - base_url: http://localhost:7878 - api_key: YOUR_KEY_HERE - - # Which quality definition in the guide to sync to Radarr. Only choice right now is 'movie' + # See: https://recyclarr.dev/wiki/yaml/config-reference/quality-definition/ quality_definition: - type: movie - # Set to 'true' to automatically remove custom formats from Radarr when they are removed from - # the guide or your configuration. This will NEVER delete custom formats you manually created! - delete_old_custom_formats: false + # See: http://localhost:3000/wiki/yaml/config-reference/quality-profiles/ + quality_profiles: + # See: http://localhost:3000/wiki/yaml/config-reference/custom-formats/ custom_formats: - # A list of custom formats to sync to Radarr. - # Use `recyclarr list custom-formats radarr` for values you can put here. - # https://trash-guides.info/Radarr/Radarr-collection-of-custom-formats/ - - trash_ids: - - ed38b889b31be83fda192888e2286d83 # BR-DISK - - 90cedc1fea7ea5d11298bebd3d1d3223 # EVO (no WEBDL) - - 90a6f9a284dff5103f6346090e6280c8 # LQ - - dc98083864ea246d05a42df0d05f81cc # x265 (720/1080p) - - b8cd450cbfa689c0259a01d9e29ba3d6 # 3D - # Uncomment the below properties to specify one or more quality profiles that should be - # updated with scores from the guide for each custom format. Without this, custom formats - # are synced to Radarr but no scores are set in any quality profiles. - #quality_profiles: - # - name: Quality Profile 1 - # - name: Quality Profile 2 - # #score: -9999 # Optional score to assign to all CFs. Overrides scores in the guide. - # #reset_unmatched_scores: true # Optionally set other scores to 0 if they are not listed in 'names' above. + # See: http://localhost:3000/wiki/yaml/config-reference/media-naming/ + media_naming: diff --git a/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilities.cs b/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilities.cs index a05ff859..da9aad56 100644 --- a/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilities.cs +++ b/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilities.cs @@ -2,9 +2,16 @@ namespace Recyclarr.Compatibility.Sonarr; public record SonarrCapabilities { - public static Version MinimumVersion { get; } = new("3.0.9.1549"); + public SonarrCapabilities() + { + } - public Version Version { get; init; } = new(); + public SonarrCapabilities(Version version) + { + Version = version; + } + + public static Version MinimumVersion { get; } = new("4.0.0.0"); - public bool SupportsCustomFormats { get; init; } + public Version Version { get; init; } = new(); } diff --git a/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilityEnforcer.cs b/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilityEnforcer.cs index a2ba61b9..dbe81987 100644 --- a/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilityEnforcer.cs +++ b/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilityEnforcer.cs @@ -1,4 +1,3 @@ -using Recyclarr.Common.Extensions; using Recyclarr.Config.Models; namespace Recyclarr.Compatibility.Sonarr; @@ -15,36 +14,5 @@ public class SonarrCapabilityEnforcer(ISonarrCapabilityFetcher capabilityFetcher $"Your Sonarr version {capabilities.Version} does not meet the minimum " + $"required version of {SonarrCapabilities.MinimumVersion}."); } - - switch (capabilities.SupportsCustomFormats) - { - case true when config.ReleaseProfiles.IsNotEmpty(): - throw new ServiceIncompatibilityException( - "Release profiles require Sonarr v3. " + - "Please use `custom_formats` instead or use the right version of Sonarr."); - - case false when config.CustomFormats.IsNotEmpty(): - throw new ServiceIncompatibilityException( - "Custom formats require Sonarr v4 or greater. " + - "Please use `release_profiles` instead or use the right version of Sonarr."); - } - - // Check for aspects of quality profile sync that are not supported by Sonarr v3 - if (!capabilities.SupportsCustomFormats) - { - if (config.QualityProfiles.Any(x => x.UpgradeUntilScore is not null)) - { - throw new ServiceIncompatibilityException( - "`until_score` under `upgrade` is not supported by Sonarr v3. " + - "Remove the until_score property or use Sonarr v4."); - } - - if (config.QualityProfiles.Any(x => x.MinFormatScore is not null)) - { - throw new ServiceIncompatibilityException( - "`min_format_score` under `quality_profiles` is not supported by Sonarr v3. " + - "Remove the min_format_score property or use Sonarr v4."); - } - } } } diff --git a/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilityFetcher.cs b/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilityFetcher.cs index f4fd13f0..24991351 100644 --- a/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilityFetcher.cs +++ b/src/Recyclarr.Compatibility/Sonarr/SonarrCapabilityFetcher.cs @@ -7,10 +7,7 @@ public class SonarrCapabilityFetcher(IServiceInformation info) { return new SonarrCapabilities { - Version = version, - - SupportsCustomFormats = - version >= new Version(4, 0) + Version = version }; } } diff --git a/src/Recyclarr.Config/ConfigExtensions.cs b/src/Recyclarr.Config/ConfigExtensions.cs index 70832d55..d42a9f00 100644 --- a/src/Recyclarr.Config/ConfigExtensions.cs +++ b/src/Recyclarr.Config/ConfigExtensions.cs @@ -25,7 +25,7 @@ public static class ConfigExtensions this IEnumerable configs, ConfigFilterCriteria criteria) { - // later, if we filter by "operation type" (e.g. release profiles, CFs, quality sizes) it's just another + // later, if we filter by "operation type" (e.g. CFs, quality sizes) it's just another // ".Where()" in the LINQ expression below. return configs.GetConfigsOfType(criteria.Service) .Where(x => criteria.Instances.IsEmpty() || diff --git a/src/Recyclarr.Config/Models/SonarrConfiguration.cs b/src/Recyclarr.Config/Models/SonarrConfiguration.cs index 35b8f7e5..9a023d90 100644 --- a/src/Recyclarr.Config/Models/SonarrConfiguration.cs +++ b/src/Recyclarr.Config/Models/SonarrConfiguration.cs @@ -6,26 +6,9 @@ public record SonarrConfiguration : ServiceConfiguration { public override SupportedServices ServiceType => SupportedServices.Sonarr; - public IList ReleaseProfiles { get; init; } = - Array.Empty(); - public SonarrMediaNamingConfig MediaNaming { get; init; } = new(); } -public class ReleaseProfileConfig -{ - public IReadOnlyCollection TrashIds { get; init; } = Array.Empty(); - public bool StrictNegativeScores { get; init; } - public IReadOnlyCollection Tags { get; init; } = Array.Empty(); - public SonarrProfileFilterConfig? Filter { get; init; } -} - -public class SonarrProfileFilterConfig -{ - public IReadOnlyCollection Include { get; init; } = Array.Empty(); - public IReadOnlyCollection Exclude { get; init; } = Array.Empty(); -} - public record SonarrMediaNamingConfig { public string? Season { get; init; } diff --git a/src/Recyclarr.Config/Parsing/ConfigYamlDataObjects.Sonarr.cs b/src/Recyclarr.Config/Parsing/ConfigYamlDataObjects.Sonarr.cs index b36681e7..fddb81a5 100644 --- a/src/Recyclarr.Config/Parsing/ConfigYamlDataObjects.Sonarr.cs +++ b/src/Recyclarr.Config/Parsing/ConfigYamlDataObjects.Sonarr.cs @@ -2,22 +2,6 @@ using JetBrains.Annotations; namespace Recyclarr.Config.Parsing; -[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] -public record ReleaseProfileFilterConfigYaml -{ - public IReadOnlyCollection? Include { get; init; } - public IReadOnlyCollection? Exclude { get; init; } -} - -[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] -public record ReleaseProfileConfigYaml -{ - public IReadOnlyCollection? TrashIds { get; init; } - public bool StrictNegativeScores { get; init; } - public IReadOnlyCollection? Tags { get; init; } - public ReleaseProfileFilterConfigYaml? Filter { get; init; } -} - [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public record SonarrEpisodeNamingConfigYaml { @@ -38,6 +22,5 @@ public record SonarrMediaNamingConfigYaml [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public record SonarrConfigYaml : ServiceConfigYaml { - public IReadOnlyCollection? ReleaseProfiles { get; init; } public SonarrMediaNamingConfigYaml? MediaNaming { get; init; } } diff --git a/src/Recyclarr.Config/Parsing/ConfigYamlDataObjectsValidation.cs b/src/Recyclarr.Config/Parsing/ConfigYamlDataObjectsValidation.cs index c774a00a..f832486d 100644 --- a/src/Recyclarr.Config/Parsing/ConfigYamlDataObjectsValidation.cs +++ b/src/Recyclarr.Config/Parsing/ConfigYamlDataObjectsValidation.cs @@ -206,45 +206,6 @@ public class SonarrConfigYamlValidator : CustomValidator public SonarrConfigYamlValidator() { Include(new ServiceConfigYamlValidator()); - - RuleFor(x => x) - .Must(x => OnlyOneHasElements(x.ReleaseProfiles, x.CustomFormats)) - .WithMessage("`custom_formats` and `release_profiles` may not be used together"); - - RuleForEach(x => x.ReleaseProfiles).SetValidator(new ReleaseProfileConfigYamlValidator()); - } -} - -public class ReleaseProfileConfigYamlValidator : CustomValidator -{ - public ReleaseProfileConfigYamlValidator() - { - RuleFor(x => x.TrashIds).NotEmpty() - .WithMessage("'trash_ids' is required for 'release_profiles' elements"); - - RuleFor(x => x.Filter) - .SetNonNullableValidator(new ReleaseProfileFilterConfigYamlValidator()); - } -} - -public class ReleaseProfileFilterConfigYamlValidator : CustomValidator -{ - public ReleaseProfileFilterConfigYamlValidator() - { - // Include & Exclude may not be used together - RuleFor(x => x) - .Must(x => OnlyOneHasElements(x.Include, x.Exclude)) - .WithMessage("'include' and 'exclude' may not be used together") - .DependentRules(() => - { - RuleFor(x => x.Include).NotEmpty() - .When(x => x.Include is not null) - .WithMessage("'include' under 'filter' must have at least 1 Trash ID"); - - RuleFor(x => x.Exclude).NotEmpty() - .When(x => x.Exclude is not null) - .WithMessage("'exclude' under 'filter' must have at least 1 Trash ID"); - }); } } diff --git a/src/Recyclarr.Config/Parsing/ConfigYamlMapperProfile.cs b/src/Recyclarr.Config/Parsing/ConfigYamlMapperProfile.cs index e335c68f..d463f3c1 100644 --- a/src/Recyclarr.Config/Parsing/ConfigYamlMapperProfile.cs +++ b/src/Recyclarr.Config/Parsing/ConfigYamlMapperProfile.cs @@ -12,8 +12,6 @@ public class ConfigYamlMapperProfile : Profile CreateMap(); CreateMap(); CreateMap(); - CreateMap(); - CreateMap(); CreateMap(); CreateMap(); diff --git a/src/Recyclarr.Config/Parsing/ErrorHandling/ConfigContextualMessages.cs b/src/Recyclarr.Config/Parsing/ErrorHandling/ConfigContextualMessages.cs index 0e6a9e81..dac4330a 100644 --- a/src/Recyclarr.Config/Parsing/ErrorHandling/ConfigContextualMessages.cs +++ b/src/Recyclarr.Config/Parsing/ErrorHandling/ConfigContextualMessages.cs @@ -34,6 +34,14 @@ public static class ConfigContextualMessages "See: https://recyclarr.dev/wiki/upgrade-guide/v6.0/#reset-scores"; } + if (msg.Contains("Property 'release_profiles' not found on type")) + { + return + "Release profiles and Sonarr v3 in general are no longer supported. All instances of " + + "`release_profiles` in your configuration YAML must be removed. " + + "https://recyclarr.dev/wiki/upgrade-guide/v7.0/#sonarr-v3-removal"; + } + return null; } } diff --git a/src/Recyclarr.Config/Parsing/PostProcessing/ConfigMerging/SonarrConfigMerger.cs b/src/Recyclarr.Config/Parsing/PostProcessing/ConfigMerging/SonarrConfigMerger.cs index 32e143e2..61530734 100644 --- a/src/Recyclarr.Config/Parsing/PostProcessing/ConfigMerging/SonarrConfigMerger.cs +++ b/src/Recyclarr.Config/Parsing/PostProcessing/ConfigMerging/SonarrConfigMerger.cs @@ -8,9 +8,6 @@ public class SonarrConfigMerger : ServiceConfigMerger { return base.Merge(a, b) with { - ReleaseProfiles = Combine(a.ReleaseProfiles, b.ReleaseProfiles, - (x, y) => x.Concat(y).ToList()), - MediaNaming = Combine(a.MediaNaming, b.MediaNaming, MergeMediaNaming) }; } diff --git a/src/Recyclarr.Repo/RepoMetadata.cs b/src/Recyclarr.Repo/RepoMetadata.cs index b149de16..19c21a5c 100644 --- a/src/Recyclarr.Repo/RepoMetadata.cs +++ b/src/Recyclarr.Repo/RepoMetadata.cs @@ -9,7 +9,6 @@ public record RadarrMetadata public record SonarrMetadata { - public IReadOnlyCollection ReleaseProfiles { get; init; } = Array.Empty(); public IReadOnlyCollection Qualities { get; init; } = Array.Empty(); public IReadOnlyCollection CustomFormats { get; init; } = Array.Empty(); public IReadOnlyCollection Naming { get; init; } = Array.Empty(); diff --git a/src/Recyclarr.ServarrApi/ApiServicesAutofacModule.cs b/src/Recyclarr.ServarrApi/ApiServicesAutofacModule.cs index 9bb4831a..8a669c94 100644 --- a/src/Recyclarr.ServarrApi/ApiServicesAutofacModule.cs +++ b/src/Recyclarr.ServarrApi/ApiServicesAutofacModule.cs @@ -7,7 +7,6 @@ using Recyclarr.ServarrApi.MediaNaming; using Recyclarr.ServarrApi.QualityDefinition; using Recyclarr.ServarrApi.QualityProfile; using Recyclarr.ServarrApi.System; -using Recyclarr.ServarrApi.Tag; namespace Recyclarr.ServarrApi; @@ -26,7 +25,6 @@ public class ApiServicesAutofacModule : Module builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterType().As(); builder.RegisterTypes( typeof(FlurlAfterCallHandler), diff --git a/src/Recyclarr.ServarrApi/ReleaseProfile/IReleaseProfileApiService.cs b/src/Recyclarr.ServarrApi/ReleaseProfile/IReleaseProfileApiService.cs deleted file mode 100644 index f315b199..00000000 --- a/src/Recyclarr.ServarrApi/ReleaseProfile/IReleaseProfileApiService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Recyclarr.Config.Models; - -namespace Recyclarr.ServarrApi.ReleaseProfile; - -public interface IReleaseProfileApiService -{ - Task UpdateReleaseProfile(IServiceConfiguration config, SonarrReleaseProfile profile); - Task CreateReleaseProfile(IServiceConfiguration config, SonarrReleaseProfile profile); - Task> GetReleaseProfiles(IServiceConfiguration config); - Task DeleteReleaseProfile(IServiceConfiguration config, int releaseProfileId); -} diff --git a/src/Recyclarr.ServarrApi/ReleaseProfile/ReleaseProfileApiService.cs b/src/Recyclarr.ServarrApi/ReleaseProfile/ReleaseProfileApiService.cs deleted file mode 100644 index ebc468eb..00000000 --- a/src/Recyclarr.ServarrApi/ReleaseProfile/ReleaseProfileApiService.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Flurl.Http; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.Http.Servarr; - -namespace Recyclarr.ServarrApi.ReleaseProfile; - -public class ReleaseProfileApiService(IServarrRequestBuilder service) : IReleaseProfileApiService -{ - public async Task UpdateReleaseProfile(IServiceConfiguration config, SonarrReleaseProfile profile) - { - await service.Request(config, "releaseprofile", profile.Id) - .PutJsonAsync(profile); - } - - public async Task CreateReleaseProfile( - IServiceConfiguration config, - SonarrReleaseProfile profile) - { - return await service.Request(config, "releaseprofile") - .PostJsonAsync(profile) - .ReceiveJson(); - } - - public async Task> GetReleaseProfiles(IServiceConfiguration config) - { - return await service.Request(config, "releaseprofile") - .GetJsonAsync>(); - } - - public async Task DeleteReleaseProfile(IServiceConfiguration config, int releaseProfileId) - { - await service.Request(config, "releaseprofile", releaseProfileId) - .DeleteAsync(); - } -} diff --git a/src/Recyclarr.ServarrApi/ReleaseProfile/SonarrReleaseProfile.cs b/src/Recyclarr.ServarrApi/ReleaseProfile/SonarrReleaseProfile.cs deleted file mode 100644 index 5985bb17..00000000 --- a/src/Recyclarr.ServarrApi/ReleaseProfile/SonarrReleaseProfile.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text.Json.Serialization; -using JetBrains.Annotations; - -namespace Recyclarr.ServarrApi.ReleaseProfile; - -[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)] -public class SonarrPreferredTerm(int score, string term) -{ - [JsonPropertyName("key")] - public string Term { get; set; } = term; - - [JsonPropertyName("value")] - public int Score { get; set; } = score; -} - -[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)] -public class SonarrReleaseProfile -{ - public int Id { get; set; } - public bool Enabled { get; set; } - public string Name { get; set; } = ""; - public IReadOnlyCollection Required { get; set; } = new List(); - public IReadOnlyCollection Ignored { get; set; } = new List(); - public IReadOnlyCollection Preferred { get; set; } = new List(); - public bool IncludePreferredWhenRenaming { get; set; } - public int IndexerId { get; set; } - public IReadOnlyCollection Tags { get; set; } = new List(); -} diff --git a/src/Recyclarr.ServarrApi/Tag/ISonarrTagApiService.cs b/src/Recyclarr.ServarrApi/Tag/ISonarrTagApiService.cs deleted file mode 100644 index 01655b7d..00000000 --- a/src/Recyclarr.ServarrApi/Tag/ISonarrTagApiService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Recyclarr.Config.Models; - -namespace Recyclarr.ServarrApi.Tag; - -public interface ISonarrTagApiService -{ - Task> GetTags(IServiceConfiguration config); - Task CreateTag(IServiceConfiguration config, string tag); -} diff --git a/src/Recyclarr.ServarrApi/Tag/SonarrTag.cs b/src/Recyclarr.ServarrApi/Tag/SonarrTag.cs deleted file mode 100644 index 0740a094..00000000 --- a/src/Recyclarr.ServarrApi/Tag/SonarrTag.cs +++ /dev/null @@ -1,13 +0,0 @@ -using JetBrains.Annotations; -using Recyclarr.Common; - -namespace Recyclarr.ServarrApi.Tag; - -public class SonarrTag -{ - public static IEqualityComparer Comparer { get; } = - new GenericEqualityComparer((x, y) => x.Id == y.Id, x => x.Id); - - public string Label { get; [UsedImplicitly] set; } = ""; - public int Id { get; [UsedImplicitly] set; } -} diff --git a/src/Recyclarr.ServarrApi/Tag/SonarrTagApiService.cs b/src/Recyclarr.ServarrApi/Tag/SonarrTagApiService.cs deleted file mode 100644 index 24a0090f..00000000 --- a/src/Recyclarr.ServarrApi/Tag/SonarrTagApiService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Flurl.Http; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.Http.Servarr; - -namespace Recyclarr.ServarrApi.Tag; - -public class SonarrTagApiService(IServarrRequestBuilder service) : ISonarrTagApiService -{ - public async Task> GetTags(IServiceConfiguration config) - { - return await service.Request(config, "tag") - .GetJsonAsync>(); - } - - public async Task CreateTag(IServiceConfiguration config, string tag) - { - return await service.Request(config, "tag") - .PostJsonAsync(new {label = tag}) - .ReceiveJson(); - } -} diff --git a/src/Recyclarr.TrashGuide/GuideAutofacModule.cs b/src/Recyclarr.TrashGuide/GuideAutofacModule.cs index 4dba2574..02969432 100644 --- a/src/Recyclarr.TrashGuide/GuideAutofacModule.cs +++ b/src/Recyclarr.TrashGuide/GuideAutofacModule.cs @@ -2,7 +2,6 @@ using Autofac; using Recyclarr.TrashGuide.CustomFormat; using Recyclarr.TrashGuide.MediaNaming; using Recyclarr.TrashGuide.QualitySize; -using Recyclarr.TrashGuide.ReleaseProfile; namespace Recyclarr.TrashGuide; @@ -19,10 +18,6 @@ public class GuideAutofacModule : Module builder.RegisterType().As(); builder.RegisterType().As(); - // Release Profile - builder.RegisterType(); - builder.RegisterType().As().SingleInstance(); - // Quality Size builder.RegisterType().As().SingleInstance(); builder.RegisterType(); diff --git a/src/Recyclarr.TrashGuide/ReleaseProfile/IReleaseProfileGuideService.cs b/src/Recyclarr.TrashGuide/ReleaseProfile/IReleaseProfileGuideService.cs deleted file mode 100644 index 22be9c7e..00000000 --- a/src/Recyclarr.TrashGuide/ReleaseProfile/IReleaseProfileGuideService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Recyclarr.TrashGuide.ReleaseProfile; - -public interface IReleaseProfileGuideService -{ - IReadOnlyList GetReleaseProfileData(); -} diff --git a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileData.cs b/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileData.cs deleted file mode 100644 index 3965f27f..00000000 --- a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileData.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Text.Json.Serialization; -using JetBrains.Annotations; - -namespace Recyclarr.TrashGuide.ReleaseProfile; - -[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] -public record TermData -{ - [JsonPropertyName("trash_id")] - public string TrashId { get; init; } = string.Empty; - - public string Name { get; init; } = string.Empty; - public string Term { get; init; } = string.Empty; - - public sealed override string ToString() - { - return $"[TrashId: {TrashId}] [Name: {Name}] [Term: {Term}]"; - } -} - -[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] -public record PreferredTermData -{ - public int Score { get; init; } - public IReadOnlyCollection Terms { get; init; } = Array.Empty(); - - public void Deconstruct(out int score, out IReadOnlyCollection terms) - { - score = Score; - terms = Terms; - } - - public sealed override string ToString() - { - return $"[Score: {Score}] [Terms: {Terms.Count}]"; - } -} - -[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] -public record ReleaseProfileData -{ - [JsonPropertyName("trash_id")] - public string TrashId { get; init; } = string.Empty; - - public string Name { get; init; } = string.Empty; - public bool IncludePreferredWhenRenaming { get; init; } - public IReadOnlyCollection Required { get; init; } = Array.Empty(); - public IReadOnlyCollection Ignored { get; init; } = Array.Empty(); - public IReadOnlyCollection Preferred { get; init; } = Array.Empty(); - - public sealed override string ToString() - { - return $"[TrashId: {TrashId}] " + - $"[Name: {Name}] " + - $"[IncludePreferred: {IncludePreferredWhenRenaming}] " + - $"[Required: {Required.Count}] " + - $"[Ignored: {Ignored.Count}] " + - $"[Preferred: {Preferred.Count}]"; - } -} diff --git a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileDataValidationFilterer.cs b/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileDataValidationFilterer.cs deleted file mode 100644 index a2d8d91d..00000000 --- a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileDataValidationFilterer.cs +++ /dev/null @@ -1,43 +0,0 @@ -using FluentValidation.Results; -using Recyclarr.Common.FluentValidation; - -namespace Recyclarr.TrashGuide.ReleaseProfile; - -public class ReleaseProfileDataValidationFilterer(ILogger log) -{ - private void LogInvalidTerm(IReadOnlyCollection failures, string filterDescription) - { - log.Debug("Validation failed on term data ({Filter}): {Failures}", filterDescription, failures); - } - - public IEnumerable FilterTerms(IEnumerable terms) - { - return terms.IsValid(new TermDataValidator(), (e, x) => LogInvalidTerm(e, x.ToString())); - } - - public IEnumerable FilterTerms(IEnumerable terms) - { - return terms.IsValid(new PreferredTermDataValidator(), (e, x) => LogInvalidTerm(e, x.ToString())); - } - - private ReleaseProfileData FilterProfile(ReleaseProfileData profile) - { - return profile with - { - Required = FilterTerms(profile.Required).ToList(), - Ignored = FilterTerms(profile.Ignored).ToList(), - Preferred = FilterTerms(profile.Preferred).ToList() - }; - } - - public IEnumerable FilterProfiles(IEnumerable data) - { - return data - .Select(FilterProfile) - .IsValid(new ReleaseProfileDataValidator(), (e, x) => - { - log.Warning("Excluding invalid release profile: {Profile}", x.ToString()); - log.Debug("Release profile excluded for these reasons: {Reasons}", e); - }); - } -} diff --git a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileDataValidator.cs b/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileDataValidator.cs deleted file mode 100644 index 72bf5590..00000000 --- a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileDataValidator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using FluentValidation; - -namespace Recyclarr.TrashGuide.ReleaseProfile; - -public class TermDataValidator : AbstractValidator -{ - public TermDataValidator() - { - RuleFor(x => x.Term).NotEmpty(); - } -} - -public class PreferredTermDataValidator : AbstractValidator -{ - public PreferredTermDataValidator() - { - RuleFor(x => x.Terms).NotEmpty(); - RuleForEach(x => x.Terms).SetValidator(new TermDataValidator()); - } -} - -public class ReleaseProfileDataValidator : AbstractValidator -{ - public ReleaseProfileDataValidator() - { - RuleFor(x => x.Name).NotEmpty(); - RuleFor(x => x.TrashId).NotEmpty(); - RuleFor(x => x) - .Must(x => x.Required.Count != 0 || x.Ignored.Count != 0 || x.Preferred.Count != 0) - .WithMessage("Must have at least one of Required, Ignored, or Preferred terms"); - } -} diff --git a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileGuideParser.cs b/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileGuideParser.cs deleted file mode 100644 index 3a35081c..00000000 --- a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileGuideParser.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.IO.Abstractions; -using System.Text.Json; -using Recyclarr.Json; - -namespace Recyclarr.TrashGuide.ReleaseProfile; - -public class ReleaseProfileGuideParser(ILogger log) -{ - private readonly JsonSerializerOptions _jsonSettings = new(GlobalJsonSerializerSettings.Services) - { - Converters = - { - new CollectionJsonConverter(), - new TermDataConverter() - } - }; - - private async Task LoadAndParseFile(IFileInfo file) - { - try - { - await using var stream = file.OpenRead(); - return await JsonSerializer.DeserializeAsync(stream, _jsonSettings); - } - catch (JsonException e) - { - HandleJsonException(e, file); - } - catch (AggregateException ae) when (ae.InnerException is JsonException e) - { - HandleJsonException(e, file); - } - - return null; - } - - private void HandleJsonException(JsonException exception, IFileInfo file) - { - log.Warning(exception, - "Failed to parse Sonarr JSON file (This likely indicates a bug that should be " + - "reported in the TRaSH repo): {File}", file.Name); - } - - public IEnumerable GetReleaseProfileData(IEnumerable paths) - { - var tasks = JsonUtils.GetJsonFilesInDirectories(paths, log).Select(LoadAndParseFile); - var data = Task.WhenAll(tasks).Result - // Make non-nullable type and filter out null values - .Choose(x => x is not null ? (true, x) : default); - - var validator = new ReleaseProfileDataValidationFilterer(log); - return validator.FilterProfiles(data); - } -} diff --git a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileGuideService.cs b/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileGuideService.cs deleted file mode 100644 index 9a6d3dca..00000000 --- a/src/Recyclarr.TrashGuide/ReleaseProfile/ReleaseProfileGuideService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Recyclarr.Repo; - -namespace Recyclarr.TrashGuide.ReleaseProfile; - -public class ReleaseProfileGuideService : IReleaseProfileGuideService -{ - private readonly Lazy> _guideData; - - public ReleaseProfileGuideService(IRepoMetadataBuilder metadataBuilder, ReleaseProfileGuideParser parser) - { - _guideData = new Lazy>(() => - { - var metadata = metadataBuilder.GetMetadata(); - var paths = metadataBuilder.ToDirectoryInfoList(metadata.JsonPaths.Sonarr.ReleaseProfiles); - return parser.GetReleaseProfileData(paths).ToList(); - }); - } - - public IReadOnlyList GetReleaseProfileData() - { - return _guideData.Value; - } -} diff --git a/src/Recyclarr.TrashGuide/ReleaseProfile/TermDataConverter.cs b/src/Recyclarr.TrashGuide/ReleaseProfile/TermDataConverter.cs deleted file mode 100644 index f6eca616..00000000 --- a/src/Recyclarr.TrashGuide/ReleaseProfile/TermDataConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using Recyclarr.Json; - -namespace Recyclarr.TrashGuide.ReleaseProfile; - -public class TermDataConverter : JsonConverter -{ - public override TermData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType is JsonTokenType.String) - { - var str = reader.GetString(); - return str is not null ? new TermData {Term = str} : null; - } - - return JsonSerializer.Deserialize(ref reader, options.CopyOptionsWithout()); - } - - public override void Write(Utf8JsonWriter writer, TermData value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value, options.CopyOptionsWithout()); - } -} diff --git a/tests/Recyclarr.Cli.Tests/Console/Commands/ListCommandsTest.cs b/tests/Recyclarr.Cli.Tests/Console/Commands/ListCommandsTest.cs index 725a693f..8ccfe4d0 100644 --- a/tests/Recyclarr.Cli.Tests/Console/Commands/ListCommandsTest.cs +++ b/tests/Recyclarr.Cli.Tests/Console/Commands/ListCommandsTest.cs @@ -25,14 +25,4 @@ public class ListCommandsTest await updater.ReceivedWithAnyArgs().UpdateAllRepositories(default); } - - [Test, AutoMockData] - public async Task Repo_update_is_called_on_list_release_profiles( - [Frozen] IMultiRepoUpdater updater, - ListReleaseProfilesCommand sut) - { - await sut.ExecuteAsync(default!, new ListReleaseProfilesCommand.CliSettings()); - - await updater.ReceivedWithAnyArgs().UpdateAllRepositories(default); - } } diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/include_preferred_when_renaming.md b/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/include_preferred_when_renaming.md deleted file mode 100644 index 8e0590ad..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/include_preferred_when_renaming.md +++ /dev/null @@ -1,19 +0,0 @@ -# First Release Profile - -Do check mark include preferred when renaming - -This score is negative [-1] - -``` -abc -``` - -# Second Release Profile - -Do not check mark include preferred when renaming - -This score is positive [1] - -``` -xyz -``` diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/strict_negative_scores.md b/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/strict_negative_scores.md deleted file mode 100644 index cc566ede..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/strict_negative_scores.md +++ /dev/null @@ -1,13 +0,0 @@ -# Test Release Profile - -This score is negative [-1] - -``` -abc -``` - -This score is positive [0] - -``` -xyz -``` diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/test_parse_markdown_complete_doc.md b/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/test_parse_markdown_complete_doc.md deleted file mode 100644 index f5ff2902..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Data/test_parse_markdown_complete_doc.md +++ /dev/null @@ -1,22 +0,0 @@ -### Release Profile 1 - -The score is [100] - -``` -term1 -``` - -This is another Score that should not be used [200] - -#### Must not contain - -``` -term2 -term3 -``` - -#### Must contain - -``` -term4 -``` \ No newline at end of file diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Filters/ReleaseProfileDataFiltererTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Filters/ReleaseProfileDataFiltererTest.cs deleted file mode 100644 index e747666e..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Filters/ReleaseProfileDataFiltererTest.cs +++ /dev/null @@ -1,207 +0,0 @@ -using Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Tests.Pipelines.ReleaseProfile.Filters; - -[TestFixture] -public class ReleaseProfileDataFiltererTest -{ - [Test, AutoMockData] - public void Include_terms_filter_works(ReleaseProfileDataFilterer sut) - { - var filter = new[] {"1", "2"}; - var terms = new TermData[] - { - new() {TrashId = "1", Term = "term1"}, - new() {TrashId = "2", Term = "term2"}, - new() {TrashId = "3", Term = "term3"}, - new() {Term = "term4"}, - new() {Term = "term5"} - }; - - var result = sut.IncludeTerms(terms, filter); - - result.Should().BeEquivalentTo(new TermData[] - { - new() {TrashId = "1", Term = "term1"}, - new() {TrashId = "2", Term = "term2"} - }); - } - - [Test, AutoMockData] - public void Include_preferred_terms_filter_works(ReleaseProfileDataFilterer sut) - { - var filter = new[] {"1", "2"}; - var terms = new PreferredTermData[] - { - new() - { - Score = 10, Terms = new TermData[] - { - new() {TrashId = "1", Term = "term1"}, - new() {TrashId = "2", Term = "term2"}, - new() {TrashId = "3", Term = "term3"}, - new() {Term = "term4"}, - new() {Term = "term5"} - } - }, - new() - { - Score = 20, Terms = new TermData[] - { - new() {TrashId = "4", Term = "term4"} - } - } - }; - - var result = sut.IncludeTerms(terms, filter); - - result.Should().BeEquivalentTo(new PreferredTermData[] - { - new() - { - Score = 10, Terms = new TermData[] - { - new() {TrashId = "1", Term = "term1"}, - new() {TrashId = "2", Term = "term2"} - } - } - }); - } - - [Test, AutoMockData] - public void Exclude_terms_filter_works(ReleaseProfileDataFilterer sut) - { - var filter = new[] {"1", "2"}; - var terms = new TermData[] - { - new() {TrashId = "1", Term = "term1"}, - new() {TrashId = "2", Term = "term2"}, - new() {TrashId = "3", Term = "term3"}, - new() {Term = "term4"}, - new() {Term = "term5"} - }; - - var result = sut.ExcludeTerms(terms, filter); - - result.Should().BeEquivalentTo(new TermData[] - { - new() {TrashId = "3", Term = "term3"}, - new() {Term = "term4"}, - new() {Term = "term5"} - }); - } - - [Test, AutoMockData] - public void Exclude_preferred_terms_filter_works(ReleaseProfileDataFilterer sut) - { - var filter = new[] {"1", "2"}; - var terms = new PreferredTermData[] - { - new() - { - Score = 10, - Terms = new TermData[] - { - new() {TrashId = "1", Term = "term1"}, - new() {TrashId = "2", Term = "term2"}, - new() {TrashId = "3", Term = "term3"}, - new() {Term = "term4"}, - new() {Term = "term5"} - } - }, - new() - { - Score = 20, - Terms = new TermData[] - { - new() {TrashId = "4", Term = "term4"}, - new() {Term = "term6"}, - new() {Term = "term7"} - } - } - }; - - var result = sut.ExcludeTerms(terms, filter); - - result.Should().BeEquivalentTo(new PreferredTermData[] - { - new() - { - Score = 10, - Terms = new TermData[] - { - new() {TrashId = "3", Term = "term3"}, - new() {Term = "term4"}, - new() {Term = "term5"} - } - }, - new() - { - Score = 20, - Terms = new TermData[] - { - new() {TrashId = "4", Term = "term4"}, - new() {Term = "term6"}, - new() {Term = "term7"} - } - } - }); - } - - [Test, AutoMockData] - public void Filter_profile_data_with_invalid_terms(ReleaseProfileDataFilterer sut) - { - var profileData = new ReleaseProfileData - { - Preferred = new PreferredTermData[] - { - new() - { - Score = 10, Terms = new TermData[] - { - new() {TrashId = "1", Term = "term1"}, // excluded by filter - new() {TrashId = "2", Term = ""}, // excluded because it's invalid - new() {TrashId = "3", Term = "term3"} - } - }, - new() - { - Score = 20, Terms = new TermData[] - { - new() {TrashId = "4", Term = "term4"} - } - } - } - }; - - var filter = new SonarrProfileFilterConfig - { - Exclude = new[] {"1"} - }; - - var result = sut.FilterProfile(profileData, filter); - - result.Should().BeEquivalentTo(new ReleaseProfileData - { - Preferred = new PreferredTermData[] - { - new() - { - Score = 10, Terms = new TermData[] - { - new() {TrashId = "3", Term = "term3"} - } - }, - new() - { - Score = 20, Terms = new TermData[] - { - new() {TrashId = "4", Term = "term4"} - } - } - } - }); - } -} diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Filters/StrictNegativeScoresFilterTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Filters/StrictNegativeScoresFilterTest.cs deleted file mode 100644 index 78e93a7d..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/Filters/StrictNegativeScoresFilterTest.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Recyclarr.Cli.Pipelines.ReleaseProfile.Filters; -using Recyclarr.Config.Models; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Cli.Tests.Pipelines.ReleaseProfile.Filters; - -[TestFixture] -public class StrictNegativeScoresFilterTest -{ - private static readonly ReleaseProfileData TestProfile = new() - { - Preferred = new[] - { - new PreferredTermData - { - Score = -1, - Terms = new[] - { - new TermData - { - TrashId = "abc", - Term = "a" - } - } - } - } - }; - - [Test, AutoMockData] - public void Preferred_with_negative_scores_is_treated_as_ignored_when_strict_negative_scores_enabled( - StrictNegativeScoresFilter sut) - { - var config = new ReleaseProfileConfig - { - StrictNegativeScores = true - }; - - var result = sut.Transform(TestProfile, config); - - result.Preferred.Should().BeEmpty(); - result.Ignored.Should().BeEquivalentTo(TestProfile.Preferred.First().Terms); - } - - [Test, AutoMockData] - public void Preferred_and_ignored_untouched_when_strict_negative_scores_disabled(StrictNegativeScoresFilter sut) - { - var config = new ReleaseProfileConfig - { - StrictNegativeScores = false - }; - - var result = sut.Transform(TestProfile, config); - result.Should().BeSameAs(TestProfile); - } -} diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/ReleaseProfileDataListerTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/ReleaseProfileDataListerTest.cs deleted file mode 100644 index 0c328634..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/ReleaseProfile/ReleaseProfileDataListerTest.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Recyclarr.Cli.Pipelines.ReleaseProfile; -using Recyclarr.TrashGuide.ReleaseProfile; -using Spectre.Console.Testing; - -namespace Recyclarr.Cli.Tests.Pipelines.ReleaseProfile; - -[TestFixture] -public class ReleaseProfileDataListerTest -{ - [Test, AutoMockData] - public void Release_profiles_appear_in_console_output( - [Frozen(Matching.ImplementedInterfaces)] TestConsole console, - [Frozen] IReleaseProfileGuideService guide, - ReleaseProfileDataLister sut) - { - var testData = new[] - { - new ReleaseProfileData {Name = "First", TrashId = "123"}, - new ReleaseProfileData {Name = "Second", TrashId = "456"} - }; - - guide.GetReleaseProfileData().Returns(testData); - - sut.ListReleaseProfiles(); - - console.Output.Should().ContainAll( - testData.SelectMany(x => new[] {x.Name, x.TrashId})); - } - - [Test, AutoMockData] - public void Terms_appear_in_console_output( - [Frozen] IReleaseProfileGuideService guide, - [Frozen(Matching.ImplementedInterfaces)] TestConsole console, - ReleaseProfileDataLister sut) - { - var requiredData = new[] - { - new TermData {Name = "First", TrashId = "111", Term = "term1"}, - new TermData {Name = "Second", TrashId = "222", Term = "term2"} - }; - - var ignoredData = new[] - { - new TermData {Name = "Third", TrashId = "333", Term = "term3"}, - new TermData {Name = "Fourth", TrashId = "444", Term = "term4"} - }; - - var preferredData = new[] - { - new TermData {Name = "Fifth", TrashId = "555", Term = "term5"}, - new TermData {Name = "Sixth", TrashId = "666", Term = "term6"} - }; - - guide.GetReleaseProfileData().Returns(new[] - { - new ReleaseProfileData - { - Name = "Release Profile", - TrashId = "098", - Required = requiredData, - Ignored = ignoredData, - Preferred = new PreferredTermData[] - { - new() {Score = 100, Terms = preferredData} - } - } - }); - - sut.ListTerms("098"); - - var expectedOutput = new[] - { - requiredData.SelectMany(x => new[] {x.Name, x.TrashId}), - ignoredData.SelectMany(x => new[] {x.Name, x.TrashId}), - preferredData.SelectMany(x => new[] {x.Name, x.TrashId}) - }; - - console.Output.Should().ContainAll(expectedOutput.SelectMany(x => x)); - } -} diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagApiFetchPhaseTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagApiFetchPhaseTest.cs deleted file mode 100644 index ec9b2eea..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagApiFetchPhaseTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Recyclarr.Cli.Pipelines.Tags; -using Recyclarr.Cli.Pipelines.Tags.PipelinePhases; -using Recyclarr.ServarrApi.Tag; - -namespace Recyclarr.Cli.Tests.Pipelines.Tags.PipelinePhases; - -[TestFixture] -public class TagApiFetchPhaseTest -{ - [Test, AutoMockData] - public async Task Cache_is_updated( - [Frozen] ISonarrTagApiService api, - [Frozen] ServiceTagCache cache, - TagPipelineContext context, - TagApiFetchPhase sut) - { - var expectedData = new[] - { - new SonarrTag {Id = 3}, - new SonarrTag {Id = 4}, - new SonarrTag {Id = 5} - }; - - api.GetTags(default!).ReturnsForAnyArgs(expectedData); - - await sut.Execute(context, default!); - cache.Tags.Should().BeEquivalentTo(expectedData); - } -} diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagApiPersistencePhaseTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagApiPersistencePhaseTest.cs deleted file mode 100644 index ec0825db..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagApiPersistencePhaseTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Recyclarr.Cli.Pipelines.Tags; -using Recyclarr.Cli.Pipelines.Tags.PipelinePhases; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.Tag; - -namespace Recyclarr.Cli.Tests.Pipelines.Tags.PipelinePhases; - -[TestFixture] -public class TagApiPersistencePhaseTest -{ - [Test, AutoMockData] - public async Task Persisted_tags_are_added_to_cache( - [Frozen] ISonarrTagApiService api, - [Frozen] ServiceTagCache cache, - TagApiPersistencePhase sut) - { - cache.AddTags(new[] - { - new SonarrTag {Id = 1}, - new SonarrTag {Id = 2} - }); - - var config = Substitute.For(); - var context = new TagPipelineContext - { - TransactionOutput = new[] {"three", "four"} - }; - - api.CreateTag(config, "three").Returns(new SonarrTag {Id = 3}); - api.CreateTag(config, "four").Returns(new SonarrTag {Id = 4}); - - await sut.Execute(context, config); - - cache.Tags.Should().BeEquivalentTo(new[] - { - new SonarrTag {Id = 1}, - new SonarrTag {Id = 2}, - new SonarrTag {Id = 3}, - new SonarrTag {Id = 4} - }); - } -} diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagConfigPhaseTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagConfigPhaseTest.cs deleted file mode 100644 index c5ac0eac..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagConfigPhaseTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Recyclarr.Cli.Pipelines.Tags; -using Recyclarr.Cli.Pipelines.Tags.PipelinePhases; -using Recyclarr.Config.Models; -using Recyclarr.Tests.TestLibrary; - -namespace Recyclarr.Cli.Tests.Pipelines.Tags.PipelinePhases; - -[TestFixture] -public class TagConfigPhaseTest -{ - [Test, AutoMockData] - public async Task Output_empty_when_config_has_no_tags(TagConfigPhase sut) - { - var context = new TagPipelineContext(); - var config = NewConfig.Sonarr() with - { - ReleaseProfiles = Array.Empty() - }; - - await sut.Execute(context, config); - context.ConfigOutput.Should().BeEmpty(); - } - - [Test, AutoMockData] - public void Output_not_empty_when_config_has_tags(TagConfigPhase sut) - { - var config = NewConfig.Sonarr() with - { - ReleaseProfiles = new[] - { - new ReleaseProfileConfig - { - Tags = new[] {"one", "two", "three"} - } - } - }; - - var context = new TagPipelineContext(); - sut.Execute(context, config); - context.ConfigOutput.Should().BeEquivalentTo(config.ReleaseProfiles[0].Tags); - } -} diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagTransactionPhaseTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagTransactionPhaseTest.cs deleted file mode 100644 index 2778a965..00000000 --- a/tests/Recyclarr.Cli.Tests/Pipelines/Tags/PipelinePhases/TagTransactionPhaseTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Recyclarr.Cli.Pipelines.Tags; -using Recyclarr.Cli.Pipelines.Tags.PipelinePhases; -using Recyclarr.Config.Models; -using Recyclarr.ServarrApi.Tag; - -namespace Recyclarr.Cli.Tests.Pipelines.Tags.PipelinePhases; - -[TestFixture] -public class TagTransactionPhaseTest -{ - [Test, AutoMockData] - public void Return_tags_in_config_that_do_not_exist_in_service(TagTransactionPhase sut) - { - var context = new TagPipelineContext - { - ConfigOutput = new[] {"one", "two", "three"}, - ApiFetchOutput = new[] - { - new SonarrTag {Label = "three"}, - new SonarrTag {Label = "four"} - } - }; - - sut.Execute(context, Substitute.For()); - - context.TransactionOutput.Should().BeEquivalentTo("one", "two"); - } - - [Test, AutoMockData] - public void Return_all_tags_if_none_exist(TagTransactionPhase sut) - { - var context = new TagPipelineContext - { - ConfigOutput = new[] {"one", "two", "three"}, - ApiFetchOutput = Array.Empty() - }; - - sut.Execute(context, Substitute.For()); - - context.TransactionOutput.Should().BeEquivalentTo("one", "two", "three"); - } - - [Test, AutoMockData] - public void No_tags_returned_if_all_exist(TagTransactionPhase sut) - { - var context = new TagPipelineContext - { - ConfigOutput = Array.Empty(), - ApiFetchOutput = new[] - { - new SonarrTag {Label = "three"}, - new SonarrTag {Label = "four"} - } - }; - - sut.Execute(context, Substitute.For()); - - context.TransactionOutput.Should().BeEmpty(); - } -} diff --git a/tests/Recyclarr.IntegrationTests/ConfigurationLoaderSecretsTest.cs b/tests/Recyclarr.IntegrationTests/ConfigurationLoaderSecretsTest.cs index 83dbb3c9..4f685290 100644 --- a/tests/Recyclarr.IntegrationTests/ConfigurationLoaderSecretsTest.cs +++ b/tests/Recyclarr.IntegrationTests/ConfigurationLoaderSecretsTest.cs @@ -19,7 +19,7 @@ public class ConfigurationLoaderSecretsTest : IntegrationTestFixture instance1: api_key: !secret api_key base_url: !secret 123GARBAGE_ - release_profiles: + custom_formats: - trash_ids: - !secret secret_rp """; @@ -39,7 +39,7 @@ public class ConfigurationLoaderSecretsTest : IntegrationTestFixture InstanceName = "instance1", ApiKey = "95283e6b156c42f3af8a9b16173f876b", BaseUrl = new Uri("https://radarr:7878"), - ReleaseProfiles = new[] + CustomFormats = new[] { new { @@ -123,10 +123,10 @@ public class ConfigurationLoaderSecretsTest : IntegrationTestFixture instance5: api_key: fake_key base_url: fake_url - release_profiles: !secret bogus_profile + custom_formats: !secret bogus_profile """; - const string secretsYml = @"bogus_profile: 95283e6b156c42f3af8a9b16173f876b"; + const string secretsYml = "bogus_profile: 95283e6b156c42f3af8a9b16173f876b"; Fs.AddFile(Paths.AppDataDirectory.File("recyclarr.yml").FullName, new MockFileData(secretsYml)); configLoader.Load(() => new StringReader(testYml)) diff --git a/tests/Recyclarr.IntegrationTests/ConfigurationLoaderTest.cs b/tests/Recyclarr.IntegrationTests/ConfigurationLoaderTest.cs index a252aa45..805ac090 100644 --- a/tests/Recyclarr.IntegrationTests/ConfigurationLoaderTest.cs +++ b/tests/Recyclarr.IntegrationTests/ConfigurationLoaderTest.cs @@ -101,26 +101,7 @@ public class ConfigurationLoaderTest : IntegrationTestFixture ApiKey = "95283e6b156c42f3af8a9b16173f876b", BaseUrl = new Uri("http://localhost:8989"), InstanceName = "name", - ReplaceExistingCustomFormats = false, - ReleaseProfiles = new List - { - new() - { - TrashIds = new[] {"123"}, - StrictNegativeScores = true, - Tags = new List {"anime"} - }, - new() - { - TrashIds = new[] {"456"}, - StrictNegativeScores = false, - Tags = new List - { - "tv", - "series" - } - } - } + ReplaceExistingCustomFormats = false } }); } diff --git a/tests/Recyclarr.IntegrationTests/Data/Load_UsingStream_CorrectParsing.yml b/tests/Recyclarr.IntegrationTests/Data/Load_UsingStream_CorrectParsing.yml index 78f74aa2..a621710c 100644 --- a/tests/Recyclarr.IntegrationTests/Data/Load_UsingStream_CorrectParsing.yml +++ b/tests/Recyclarr.IntegrationTests/Data/Load_UsingStream_CorrectParsing.yml @@ -2,12 +2,3 @@ name: base_url: http://localhost:8989 api_key: 95283e6b156c42f3af8a9b16173f876b - release_profiles: - - trash_ids: [123] - strict_negative_scores: true - tags: - - anime - - trash_ids: [456] - tags: - - tv - - series diff --git a/tests/Recyclarr.Tests/Compatibility/Sonarr/SonarrCapabilityEnforcerTest.cs b/tests/Recyclarr.Tests/Compatibility/Sonarr/SonarrCapabilityEnforcerTest.cs index 993e0b32..e6857e1a 100644 --- a/tests/Recyclarr.Tests/Compatibility/Sonarr/SonarrCapabilityEnforcerTest.cs +++ b/tests/Recyclarr.Tests/Compatibility/Sonarr/SonarrCapabilityEnforcerTest.cs @@ -1,6 +1,5 @@ using Recyclarr.Compatibility; using Recyclarr.Compatibility.Sonarr; -using Recyclarr.Config.Models; using Recyclarr.Tests.TestLibrary; namespace Recyclarr.Tests.Compatibility.Sonarr; @@ -16,88 +15,11 @@ public class SonarrCapabilityEnforcerTest var config = NewConfig.Sonarr(); var min = SonarrCapabilities.MinimumVersion; - fetcher.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities - { - Version = new Version(min.Major, min.Minor, min.Build, min.Revision - 1) - }); + fetcher.GetCapabilities(default!).ReturnsForAnyArgs( + new SonarrCapabilities(new Version(min.Major - 1, min.Minor, min.Build, min.Revision))); var act = () => sut.Check(config); act.Should().ThrowAsync().WithMessage("*minimum*"); } - - [Test, AutoMockData] - public void Release_profiles_not_allowed_in_v4( - [Frozen] ISonarrCapabilityFetcher fetcher, - SonarrCapabilityEnforcer sut) - { - var config = NewConfig.Sonarr() with - { - ReleaseProfiles = new List - { - new() - } - }; - - fetcher.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities - { - SupportsCustomFormats = true - }); - - var act = () => sut.Check(config); - - act.Should().ThrowAsync().WithMessage("*v3*"); - } - - [Test, AutoMockData] - public void Custom_formats_not_allowed_in_v3( - [Frozen] ISonarrCapabilityFetcher fetcher, - SonarrCapabilityEnforcer sut) - { - var config = NewConfig.Sonarr() with - { - CustomFormats = new List - { - new() - } - }; - - fetcher.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities - { - SupportsCustomFormats = false - }); - - var act = () => sut.Check(config); - - act.Should().ThrowAsync().WithMessage("*custom formats*v4*"); - } - - [Test, AutoMockData] - public void Qualities_not_allowed_in_v3( - [Frozen] ISonarrCapabilityFetcher fetcher, - SonarrCapabilityEnforcer sut) - { - var config = NewConfig.Sonarr() with - { - QualityProfiles = new[] - { - new QualityProfileConfig - { - Qualities = new[] - { - new QualityProfileQualityConfig() - } - } - } - }; - - fetcher.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities - { - SupportsCustomFormats = false - }); - - var act = () => sut.Check(config); - - act.Should().ThrowAsync().WithMessage("*qualities*v4*"); - } } diff --git a/tests/Recyclarr.Tests/Config/Parsing/PostProcessing/ConfigMerging/MergeReleaseProfilesTest.cs b/tests/Recyclarr.Tests/Config/Parsing/PostProcessing/ConfigMerging/MergeReleaseProfilesTest.cs deleted file mode 100644 index aca44bc9..00000000 --- a/tests/Recyclarr.Tests/Config/Parsing/PostProcessing/ConfigMerging/MergeReleaseProfilesTest.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Recyclarr.Config.Parsing; -using Recyclarr.Config.Parsing.PostProcessing.ConfigMerging; - -namespace Recyclarr.Tests.Config.Parsing.PostProcessing.ConfigMerging; - -[TestFixture] -public class MergeReleaseProfilesTest -{ - [Test] - public void Empty_right_to_non_empty_left() - { - var leftConfig = new SonarrConfigYaml - { - ReleaseProfiles = new[] - { - new ReleaseProfileConfigYaml - { - TrashIds = new[] {"id1"}, - Filter = new ReleaseProfileFilterConfigYaml - { - Exclude = new[] {"exclude"}, - Include = new[] {"include"} - }, - Tags = new[] {"tag1", "tag2"}, - StrictNegativeScores = true - } - } - }; - - var rightConfig = new SonarrConfigYaml(); - - var sut = new SonarrConfigMerger(); - - var result = sut.Merge(leftConfig, rightConfig); - - result.Should().BeEquivalentTo(leftConfig); - } - - [Test] - public void Non_empty_right_to_empty_left() - { - var leftConfig = new SonarrConfigYaml(); - - var rightConfig = new SonarrConfigYaml - { - ReleaseProfiles = new[] - { - new ReleaseProfileConfigYaml - { - TrashIds = new[] {"id1"}, - Filter = new ReleaseProfileFilterConfigYaml - { - Exclude = new[] {"exclude"}, - Include = new[] {"include"} - }, - Tags = new[] {"tag1", "tag2"}, - StrictNegativeScores = true - } - } - }; - - var sut = new SonarrConfigMerger(); - - var result = sut.Merge(leftConfig, rightConfig); - - result.Should().BeEquivalentTo(rightConfig); - } - - [Test] - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] - public void Non_empty_right_to_non_empty_left() - { - var leftConfig = new SonarrConfigYaml - { - ReleaseProfiles = new[] - { - new ReleaseProfileConfigYaml - { - TrashIds = new[] {"id1"}, - Filter = new ReleaseProfileFilterConfigYaml - { - Exclude = new[] {"exclude1"}, - Include = new[] {"include1"} - }, - Tags = new[] {"tag1", "tag2"}, - StrictNegativeScores = true - }, - new ReleaseProfileConfigYaml - { - TrashIds = new[] {"id2", "id3"}, - Filter = new ReleaseProfileFilterConfigYaml - { - Exclude = new[] {"exclude2"}, - Include = new[] {"include2"} - }, - Tags = new[] {"tag3"}, - StrictNegativeScores = true - } - } - }; - - var rightConfig = new SonarrConfigYaml - { - ReleaseProfiles = new[] - { - new ReleaseProfileConfigYaml - { - TrashIds = new[] {"id4"}, - Filter = new ReleaseProfileFilterConfigYaml - { - Exclude = new[] {"exclude3"}, - Include = new[] {"include3"} - }, - Tags = new[] {"tag4", "tag5"}, - StrictNegativeScores = false - } - } - }; - - var sut = new SonarrConfigMerger(); - - var result = sut.Merge(leftConfig, rightConfig); - - result.Should().BeEquivalentTo(new SonarrConfigYaml - { - ReleaseProfiles = leftConfig.ReleaseProfiles.Concat(rightConfig.ReleaseProfiles).ToList() - }); - } -} diff --git a/tests/Recyclarr.Tests/Config/Parsing/SonarrConfigYamlValidatorTest.cs b/tests/Recyclarr.Tests/Config/Parsing/SonarrConfigYamlValidatorTest.cs deleted file mode 100644 index 14c1a982..00000000 --- a/tests/Recyclarr.Tests/Config/Parsing/SonarrConfigYamlValidatorTest.cs +++ /dev/null @@ -1,84 +0,0 @@ -using FluentValidation.TestHelper; -using Recyclarr.Config.Models; -using Recyclarr.Config.Parsing; - -namespace Recyclarr.Tests.Config.Parsing; - -[TestFixture] -public class SonarrConfigYamlValidatorTest -{ - [Test] - public void Validation_failure_when_rps_and_cfs_used_together() - { - var config = new SonarrConfigYaml - { - ReleaseProfiles = new[] {new ReleaseProfileConfigYaml()}, - CustomFormats = new[] {new CustomFormatConfigYaml()} - }; - - var validator = new SonarrConfigYamlValidator(); - var result = validator.TestValidate(config); - - result.ShouldHaveValidationErrorFor(x => x) - .WithErrorMessage("`custom_formats` and `release_profiles` may not be used together"); - } - - [Test] - public void Sonarr_release_profile_failures() - { - var config = new ReleaseProfileConfigYaml - { - TrashIds = Array.Empty(), - Filter = new ReleaseProfileFilterConfigYaml - { - Include = new[] {"include"}, - Exclude = new[] {"exclude"} - } - }; - - var validator = new ReleaseProfileConfigYamlValidator(); - var result = validator.TestValidate(config); - - result.Errors.Should().HaveCount(2); - - // Release profile trash IDs cannot be empty - result.ShouldHaveValidationErrorFor(x => x.TrashIds); - - // Cannot use include + exclude filters together - result.ShouldHaveValidationErrorFor(nameof(ReleaseProfileConfig.Filter)); - } - - [Test] - public void Filter_include_can_not_be_empty() - { - var config = new ReleaseProfileFilterConfigYaml - { - Include = Array.Empty(), - Exclude = new[] {"exclude"} - }; - - var validator = new ReleaseProfileFilterConfigYamlValidator(); - var result = validator.TestValidate(config); - - result.Errors.Should().HaveCount(1); - - result.ShouldHaveValidationErrorFor(x => x.Include); - } - - [Test] - public void Filter_exclude_can_not_be_empty() - { - var config = new ReleaseProfileFilterConfigYaml - { - Exclude = Array.Empty(), - Include = new[] {"exclude"} - }; - - var validator = new ReleaseProfileFilterConfigYamlValidator(); - var result = validator.TestValidate(config); - - result.Errors.Should().HaveCount(1); - - result.ShouldHaveValidationErrorFor(x => x.Exclude); - } -} diff --git a/tests/Recyclarr.Tests/Repo/TrashRepoMetadataBuilderTest.cs b/tests/Recyclarr.Tests/Repo/TrashRepoMetadataBuilderTest.cs index 1d6a7559..14cedbe3 100644 --- a/tests/Recyclarr.Tests/Repo/TrashRepoMetadataBuilderTest.cs +++ b/tests/Recyclarr.Tests/Repo/TrashRepoMetadataBuilderTest.cs @@ -17,7 +17,6 @@ public class TrashRepoMetadataBuilderTest "naming": ["docs/json/radarr/naming"] }, "sonarr": { - "release_profiles": ["docs/json/sonarr/rp"], "custom_formats": ["docs/json/sonarr/cf"], "qualities": ["docs/json/sonarr/quality-size"], "naming": ["docs/json/sonarr/naming"] diff --git a/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileDataValidationFiltererTest.cs b/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileDataValidationFiltererTest.cs deleted file mode 100644 index 40a17b26..00000000 --- a/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileDataValidationFiltererTest.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Tests.TrashGuide.ReleaseProfile; - -[TestFixture] -public class ReleaseProfileDataValidationFiltererTest -{ - [Test, AutoMockData] - public void Valid_data_is_not_filtered_out(ReleaseProfileDataValidationFilterer sut) - { - var data = new[] - { - new ReleaseProfileData - { - TrashId = "trash_id", - Name = "name", - Required = new[] {new TermData {Term = "term1"}}, - Ignored = new[] {new TermData {Term = "term2"}}, - Preferred = new[] {new PreferredTermData {Terms = new[] {new TermData {Term = "term3"}}}} - } - }; - - var result = sut.FilterProfiles(data); - - result.Should().BeEquivalentTo(data); - } - - [Test, AutoMockData] - public void Invalid_terms_are_filtered_out(ReleaseProfileDataValidationFilterer sut) - { - var data = new[] - { - new ReleaseProfileData - { - TrashId = "trash_id", - Name = "name", - Required = new[] {new TermData {Term = ""}}, - Ignored = new[] {new TermData {Term = "term2"}}, - Preferred = new[] {new PreferredTermData {Terms = new[] {new TermData {Term = "term3"}}}} - } - }; - - var result = sut.FilterProfiles(data); - - result.Should().ContainSingle().Which.Should().BeEquivalentTo(new ReleaseProfileData - { - TrashId = "trash_id", - Name = "name", - Required = Array.Empty(), - Ignored = new[] {new TermData {Term = "term2"}}, - Preferred = new[] {new PreferredTermData {Terms = new[] {new TermData {Term = "term3"}}}} - }); - } - - [Test, AutoMockData] - public void Whole_release_profile_filtered_out_if_all_terms_invalid(ReleaseProfileDataValidationFilterer sut) - { - var data = new[] - { - new ReleaseProfileData - { - TrashId = "trash_id", - Name = "name", - Required = new[] {new TermData {Term = ""}}, - Ignored = new[] {new TermData {Term = ""}}, - Preferred = new[] {new PreferredTermData {Terms = new[] {new TermData {Term = ""}}}} - } - }; - - var result = sut.FilterProfiles(data); - - result.Should().BeEmpty(); - } -} diff --git a/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileDataValidatorTest.cs b/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileDataValidatorTest.cs deleted file mode 100644 index 370989a8..00000000 --- a/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileDataValidatorTest.cs +++ /dev/null @@ -1,95 +0,0 @@ -using FluentValidation.TestHelper; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Tests.TrashGuide.ReleaseProfile; - -[TestFixture] -public class ReleaseProfileDataValidatorTest -{ - [Test] - public void Empty_term_collections_not_allowed() - { - var validator = new ReleaseProfileDataValidator(); - var data = new ReleaseProfileData(); - - validator.Validate(data).IsValid.Should().BeFalse(); - } - - [Test] - public void Allow_single_preferred_term() - { - var validator = new ReleaseProfileDataValidator(); - var data = new ReleaseProfileData - { - TrashId = "trash_id", - Name = "name", - Required = Array.Empty(), - Ignored = Array.Empty(), - Preferred = new[] {new PreferredTermData {Terms = new[] {new TermData()}}} - }; - - var result = validator.TestValidate(data); - - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - public void Allow_single_required_term() - { - var validator = new ReleaseProfileDataValidator(); - var data = new ReleaseProfileData - { - TrashId = "trash_id", - Name = "name", - Required = new[] {new TermData {Term = "term"}}, - Ignored = Array.Empty(), - Preferred = Array.Empty() - }; - - var result = validator.TestValidate(data); - - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - public void Allow_single_ignored_term() - { - var validator = new ReleaseProfileDataValidator(); - var data = new ReleaseProfileData - { - TrashId = "trash_id", - Name = "name", - Required = Array.Empty(), - Ignored = new[] {new TermData {Term = "term"}}, - Preferred = Array.Empty() - }; - - var result = validator.TestValidate(data); - - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - public void Term_data_validate_empty() - { - var validator = new TermDataValidator(); - var data = new TermData(); - - var result = validator.TestValidate(data); - - result.ShouldHaveValidationErrorFor(x => x.Term); - result.ShouldNotHaveValidationErrorFor(x => x.Name); - result.ShouldNotHaveValidationErrorFor(x => x.TrashId); - } - - [Test] - public void Preferred_term_data_validate_empty() - { - var validator = new PreferredTermDataValidator(); - var data = new PreferredTermData(); - - var result = validator.TestValidate(data); - - result.ShouldHaveValidationErrorFor(x => x.Terms); - } -} diff --git a/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileGuideServiceTest.cs b/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileGuideServiceTest.cs deleted file mode 100644 index f2171311..00000000 --- a/tests/Recyclarr.Tests/TrashGuide/ReleaseProfile/ReleaseProfileGuideServiceTest.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.IO.Abstractions; -using Recyclarr.Json; -using Recyclarr.Repo; -using Recyclarr.TestLibrary; -using Recyclarr.TrashGuide.ReleaseProfile; - -namespace Recyclarr.Tests.TrashGuide.ReleaseProfile; - -[TestFixture] -public class ReleaseProfileGuideServiceTest -{ - [Test, AutoMockData] - public void Get_release_profile_json_works( - [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, - [Frozen] IRepoMetadataBuilder metadataBuilder, - ReleaseProfileGuideService sut) - { - static ReleaseProfileData MakeMockObject(string term) - { - return new ReleaseProfileData - { - Name = "name", - TrashId = "123", - Required = new TermData[] - { - new() {Term = term} - } - }; - } - - var mockData1 = MakeMockObject("first"); - var mockData2 = MakeMockObject("second"); - var baseDir = fs.CurrentDirectory().SubDirectory("files"); - baseDir.Create(); - - fs.AddFile(baseDir.File("first.json").FullName, - MockData.FromJson(mockData1, GlobalJsonSerializerSettings.Services)); - - fs.AddFile(baseDir.File("second.json").FullName, - MockData.FromJson(mockData2, GlobalJsonSerializerSettings.Services)); - - metadataBuilder.ToDirectoryInfoList(default!).ReturnsForAnyArgs(new[] {baseDir}); - - var results = sut.GetReleaseProfileData(); - - results.Should().BeEquivalentTo(new[] - { - mockData1, - mockData2 - }); - } - - [Test, AutoMockData] - public void Json_exceptions_do_not_interrupt_parsing_other_files( - [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, - [Frozen] IRepoMetadataBuilder metadataBuilder, - ReleaseProfileGuideService sut) - { - var rootPath = fs.CurrentDirectory().SubDirectory("files"); - rootPath.Create(); - - var badData = "# comment"; - var goodData = new ReleaseProfileData - { - Name = "name", - TrashId = "123", - Required = new TermData[] - { - new() {Term = "abc"} - } - }; - - fs.AddFile(rootPath.File("0_bad_data.json").FullName, - MockData.FromString(badData)); - - fs.AddFile(rootPath.File("1_good_data.json").FullName, - MockData.FromJson(goodData, GlobalJsonSerializerSettings.Services)); - - metadataBuilder.ToDirectoryInfoList(default!).ReturnsForAnyArgs(new[] {rootPath}); - - var results = sut.GetReleaseProfileData(); - - results.Should().BeEquivalentTo(new[] {goodData}); - } -}