diff --git a/src/Sonarr.Api.V3/Config/NamingConfigController.cs b/src/Sonarr.Api.V3/Config/NamingConfigController.cs index f8870f35c..dd2d0a537 100644 --- a/src/Sonarr.Api.V3/Config/NamingConfigController.cs +++ b/src/Sonarr.Api.V3/Config/NamingConfigController.cs @@ -65,14 +65,14 @@ namespace Sonarr.Api.V3.Config } [HttpGet("examples")] - public object GetExamples([FromQuery]NamingConfigResource config) + public object GetExamples([FromQuery]NamingConfigResource settings) { - if (config.Id == 0) + if (settings.Id == 0) { - config = GetNamingConfig(); + settings = GetNamingConfig(); } - var nameSpec = config.ToModel(); + var nameSpec = settings.ToModel(); var sampleResource = new NamingExampleResource(); var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec); diff --git a/src/Sonarr.Api.V5/Settings/NamingExampleResource.cs b/src/Sonarr.Api.V5/Settings/NamingExampleResource.cs new file mode 100644 index 000000000..1eacc3f2e --- /dev/null +++ b/src/Sonarr.Api.V5/Settings/NamingExampleResource.cs @@ -0,0 +1,58 @@ +using NzbDrone.Core.Organizer; + +namespace Sonarr.Api.V5.Settings; + +public class NamingExampleResource +{ + public string? SingleEpisodeExample { get; set; } + public string? MultiEpisodeExample { get; set; } + public string? DailyEpisodeExample { get; set; } + public string? AnimeEpisodeExample { get; set; } + public string? AnimeMultiEpisodeExample { get; set; } + public string? SeriesFolderExample { get; set; } + public string? SeasonFolderExample { get; set; } + public string? SpecialsFolderExample { get; set; } +} + +public static class NamingConfigResourceMapper +{ + public static NamingSettingsResource ToResource(this NamingConfig model) + { + return new NamingSettingsResource + { + Id = model.Id, + + RenameEpisodes = model.RenameEpisodes, + ReplaceIllegalCharacters = model.ReplaceIllegalCharacters, + ColonReplacementFormat = (int)model.ColonReplacementFormat, + CustomColonReplacementFormat = model.CustomColonReplacementFormat, + MultiEpisodeStyle = (int)model.MultiEpisodeStyle, + StandardEpisodeFormat = model.StandardEpisodeFormat, + DailyEpisodeFormat = model.DailyEpisodeFormat, + AnimeEpisodeFormat = model.AnimeEpisodeFormat, + SeriesFolderFormat = model.SeriesFolderFormat, + SeasonFolderFormat = model.SeasonFolderFormat, + SpecialsFolderFormat = model.SpecialsFolderFormat + }; + } + + public static NamingConfig ToModel(this NamingSettingsResource resource) + { + return new NamingConfig + { + Id = resource.Id, + + RenameEpisodes = resource.RenameEpisodes, + ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters, + MultiEpisodeStyle = (MultiEpisodeStyle)resource.MultiEpisodeStyle, + ColonReplacementFormat = (ColonReplacementFormat)resource.ColonReplacementFormat, + CustomColonReplacementFormat = resource.CustomColonReplacementFormat ?? "", + StandardEpisodeFormat = resource.StandardEpisodeFormat, + DailyEpisodeFormat = resource.DailyEpisodeFormat, + AnimeEpisodeFormat = resource.AnimeEpisodeFormat, + SeriesFolderFormat = resource.SeriesFolderFormat, + SeasonFolderFormat = resource.SeasonFolderFormat, + SpecialsFolderFormat = resource.SpecialsFolderFormat + }; + } +} diff --git a/src/Sonarr.Api.V5/Settings/NamingSettingsController.cs b/src/Sonarr.Api.V5/Settings/NamingSettingsController.cs new file mode 100644 index 000000000..5c0210fb6 --- /dev/null +++ b/src/Sonarr.Api.V5/Settings/NamingSettingsController.cs @@ -0,0 +1,141 @@ +using FluentValidation; +using FluentValidation.Results; +using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Organizer; +using Sonarr.Http; +using Sonarr.Http.REST; +using Sonarr.Http.REST.Attributes; + +namespace Sonarr.Api.V5.Settings; + +[V5ApiController("settings/naming")] +public class NamingSettingsController : RestController +{ + private readonly INamingConfigService _namingConfigService; + private readonly IFilenameSampleService _filenameSampleService; + private readonly IFilenameValidationService _filenameValidationService; + + public NamingSettingsController(INamingConfigService namingConfigService, + IFilenameSampleService filenameSampleService, + IFilenameValidationService filenameValidationService) + { + _namingConfigService = namingConfigService; + _filenameSampleService = filenameSampleService; + _filenameValidationService = filenameValidationService; + + SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5); + SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat(); + SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat(); + SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat(); + SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat(); + SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat(); + SharedValidator.RuleFor(c => c.SpecialsFolderFormat).ValidSpecialsFolderFormat(); + SharedValidator.RuleFor(c => c.CustomColonReplacementFormat).ValidCustomColonReplacement().When(c => c.ColonReplacementFormat == (int)ColonReplacementFormat.Custom); + } + + protected override NamingSettingsResource GetResourceById(int id) + { + return GetNamingConfig(); + } + + [HttpGet] + public NamingSettingsResource GetNamingConfig() + { + var nameSpec = _namingConfigService.GetConfig(); + var resource = nameSpec.ToResource(); + + return resource; + } + + [RestPutById] + public ActionResult UpdateNamingConfig([FromBody] NamingSettingsResource resource) + { + var nameSpec = resource.ToModel(); + ValidateFormatResult(nameSpec); + + _namingConfigService.Save(nameSpec); + + return Accepted(resource.Id); + } + + [HttpGet("examples")] + public object GetExamples([FromQuery]NamingSettingsResource settings) + { + if (settings.Id == 0) + { + settings = GetNamingConfig(); + } + + var nameSpec = settings.ToModel(); + var sampleResource = new NamingExampleResource(); + + var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec); + var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec); + var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec); + var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec); + var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec); + + sampleResource.SingleEpisodeExample = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult) != null + ? null + : singleEpisodeSampleResult.FileName; + + sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null + ? null + : multiEpisodeSampleResult.FileName; + + sampleResource.DailyEpisodeExample = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult) != null + ? null + : dailyEpisodeSampleResult.FileName; + + sampleResource.AnimeEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult) != null + ? null + : animeEpisodeSampleResult.FileName; + + sampleResource.AnimeMultiEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult) != null + ? null + : animeMultiEpisodeSampleResult.FileName; + + sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace() + ? null + : _filenameSampleService.GetSeriesFolderSample(nameSpec); + + sampleResource.SeasonFolderExample = nameSpec.SeasonFolderFormat.IsNullOrWhiteSpace() + ? null + : _filenameSampleService.GetSeasonFolderSample(nameSpec); + + sampleResource.SpecialsFolderExample = nameSpec.SpecialsFolderFormat.IsNullOrWhiteSpace() + ? null + : _filenameSampleService.GetSpecialsFolderSample(nameSpec); + + return sampleResource; + } + + private void ValidateFormatResult(NamingConfig nameSpec) + { + var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec); + var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec); + var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec); + var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec); + var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec); + + var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult); + var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult); + var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult); + var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult); + var animeMultiEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult); + + var validationFailures = new List(); + + validationFailures.AddIfNotNull(singleEpisodeValidationResult); + validationFailures.AddIfNotNull(multiEpisodeValidationResult); + validationFailures.AddIfNotNull(dailyEpisodeValidationResult); + validationFailures.AddIfNotNull(animeEpisodeValidationResult); + validationFailures.AddIfNotNull(animeMultiEpisodeValidationResult); + + if (validationFailures.Any()) + { + throw new ValidationException(validationFailures.DistinctBy(v => v.PropertyName).ToArray()); + } + } +} diff --git a/src/Sonarr.Api.V5/Settings/NamingSettingsResource.cs b/src/Sonarr.Api.V5/Settings/NamingSettingsResource.cs new file mode 100644 index 000000000..272cc8dff --- /dev/null +++ b/src/Sonarr.Api.V5/Settings/NamingSettingsResource.cs @@ -0,0 +1,18 @@ +using Sonarr.Http.REST; + +namespace Sonarr.Api.V5.Settings; + +public class NamingSettingsResource : RestResource +{ + public bool RenameEpisodes { get; set; } + public bool ReplaceIllegalCharacters { get; set; } + public int ColonReplacementFormat { get; set; } + public string? CustomColonReplacementFormat { get; set; } + public int MultiEpisodeStyle { get; set; } + public string? StandardEpisodeFormat { get; set; } + public string? DailyEpisodeFormat { get; set; } + public string? AnimeEpisodeFormat { get; set; } + public string? SeriesFolderFormat { get; set; } + public string? SeasonFolderFormat { get; set; } + public string? SpecialsFolderFormat { get; set; } +}