mirror of https://github.com/Sonarr/Sonarr
parent
0c883f7886
commit
293a1bc618
|
@ -138,7 +138,8 @@ class Naming extends Component {
|
||||||
{ key: 1, value: translate('ReplaceWithDash') },
|
{ key: 1, value: translate('ReplaceWithDash') },
|
||||||
{ key: 2, value: translate('ReplaceWithSpaceDash') },
|
{ key: 2, value: translate('ReplaceWithSpaceDash') },
|
||||||
{ key: 3, value: translate('ReplaceWithSpaceDashSpace') },
|
{ key: 3, value: translate('ReplaceWithSpaceDashSpace') },
|
||||||
{ key: 4, value: translate('SmartReplace'), hint: translate('SmartReplaceHint') }
|
{ key: 4, value: translate('SmartReplace'), hint: translate('SmartReplaceHint') },
|
||||||
|
{ key: 5, value: translate('Custom'), hint: translate('CustomColonReplacementFormatHint') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const standardEpisodeFormatHelpTexts = [];
|
const standardEpisodeFormatHelpTexts = [];
|
||||||
|
@ -262,6 +263,22 @@ class Naming extends Component {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
replaceIllegalCharacters && settings.colonReplacementFormat.value === 5 ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('ColonReplacement')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="customColonReplacementFormat"
|
||||||
|
helpText={translate('CustomColonReplacementFormatHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...settings.customColonReplacementFormat}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
renameEpisodes &&
|
renameEpisodes &&
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -90,5 +90,18 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||||
.Should().Be(expected);
|
.Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("Series: Title", ColonReplacementFormat.Custom, "\ua789", "Series\ua789 Title")]
|
||||||
|
[TestCase("Series: Title", ColonReplacementFormat.Custom, "∶", "Series∶ Title")]
|
||||||
|
public void should_replace_colon_with_custom_format(string seriesName, ColonReplacementFormat replacementFormat, string customFormat, string expected)
|
||||||
|
{
|
||||||
|
_series.Title = seriesName;
|
||||||
|
_namingConfig.StandardEpisodeFormat = "{Series Title}";
|
||||||
|
_namingConfig.ColonReplacementFormat = replacementFormat;
|
||||||
|
_namingConfig.CustomColonReplacementFormat = customFormat;
|
||||||
|
|
||||||
|
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||||
|
.Should().Be(expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(211)]
|
||||||
|
public class add_custom_colon_replacement_to_naming_config : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("NamingConfig").AddColumn("CustomColonReplacementFormat").AsString().WithDefaultValue("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -270,6 +270,9 @@
|
||||||
"CreateGroup": "Create Group",
|
"CreateGroup": "Create Group",
|
||||||
"CurrentlyInstalled": "Currently Installed",
|
"CurrentlyInstalled": "Currently Installed",
|
||||||
"Custom": "Custom",
|
"Custom": "Custom",
|
||||||
|
"CustomColonReplacement": "Custom Colon Replacement",
|
||||||
|
"CustomColonReplacementFormatHelpText": "Characters to be used as a replacement for colons",
|
||||||
|
"CustomColonReplacementFormatHint": "Valid file system character such as Colon (Letter)",
|
||||||
"CustomFilter": "Custom Filter",
|
"CustomFilter": "Custom Filter",
|
||||||
"CustomFilters": "Custom Filters",
|
"CustomFilters": "Custom Filters",
|
||||||
"CustomFormat": "Custom Format",
|
"CustomFormat": "Custom Format",
|
||||||
|
|
|
@ -6,7 +6,6 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Diacritical;
|
using Diacritical;
|
||||||
using DryIoc.ImTools;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
|
@ -112,6 +111,9 @@ namespace NzbDrone.Core.Organizer
|
||||||
{ "wel", "cym" }
|
{ "wel", "cym" }
|
||||||
}.ToImmutableDictionary();
|
}.ToImmutableDictionary();
|
||||||
|
|
||||||
|
public static readonly ImmutableArray<string> BadCharacters = ImmutableArray.Create("\\", "/", "<", ">", "?", "*", "|", "\"");
|
||||||
|
public static readonly ImmutableArray<string> GoodCharacters = ImmutableArray.Create("+", "+", "", "", "!", "-", "", "");
|
||||||
|
|
||||||
public FileNameBuilder(INamingConfigService namingConfigService,
|
public FileNameBuilder(INamingConfigService namingConfigService,
|
||||||
IQualityDefinitionService qualityDefinitionService,
|
IQualityDefinitionService qualityDefinitionService,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
|
@ -1156,8 +1158,6 @@ namespace NzbDrone.Core.Organizer
|
||||||
private static string CleanFileName(string name, NamingConfig namingConfig)
|
private static string CleanFileName(string name, NamingConfig namingConfig)
|
||||||
{
|
{
|
||||||
var result = name;
|
var result = name;
|
||||||
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", "|", "\"" };
|
|
||||||
string[] goodCharacters = { "+", "+", "", "", "!", "-", "", "" };
|
|
||||||
|
|
||||||
if (namingConfig.ReplaceIllegalCharacters)
|
if (namingConfig.ReplaceIllegalCharacters)
|
||||||
{
|
{
|
||||||
|
@ -1182,6 +1182,9 @@ namespace NzbDrone.Core.Organizer
|
||||||
case ColonReplacementFormat.SpaceDashSpace:
|
case ColonReplacementFormat.SpaceDashSpace:
|
||||||
replacement = " - ";
|
replacement = " - ";
|
||||||
break;
|
break;
|
||||||
|
case ColonReplacementFormat.Custom:
|
||||||
|
replacement = namingConfig.CustomColonReplacementFormat;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = result.Replace(":", replacement);
|
result = result.Replace(":", replacement);
|
||||||
|
@ -1192,9 +1195,9 @@ namespace NzbDrone.Core.Organizer
|
||||||
result = result.Replace(":", string.Empty);
|
result = result.Replace(":", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < badCharacters.Length; i++)
|
for (var i = 0; i < BadCharacters.Length; i++)
|
||||||
{
|
{
|
||||||
result = result.Replace(badCharacters[i], namingConfig.ReplaceIllegalCharacters ? goodCharacters[i] : string.Empty);
|
result = result.Replace(BadCharacters[i], namingConfig.ReplaceIllegalCharacters ? GoodCharacters[i] : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.TrimStart(' ', '.').TrimEnd(' ');
|
return result.TrimStart(' ', '.').TrimEnd(' ');
|
||||||
|
@ -1268,6 +1271,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
Dash = 1,
|
Dash = 1,
|
||||||
SpaceDash = 2,
|
SpaceDash = 2,
|
||||||
SpaceDashSpace = 3,
|
SpaceDashSpace = 3,
|
||||||
Smart = 4
|
Smart = 4,
|
||||||
|
Custom = 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -61,6 +62,13 @@ namespace NzbDrone.Core.Organizer
|
||||||
|
|
||||||
return ruleBuilder.SetValidator(new IllegalCharactersValidator());
|
return ruleBuilder.SetValidator(new IllegalCharactersValidator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IRuleBuilderOptions<T, string> ValidCustomColonReplacement<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
|
{
|
||||||
|
ruleBuilder.SetValidator(new IllegalColonCharactersValidator());
|
||||||
|
|
||||||
|
return ruleBuilder.SetValidator(new IllegalCharactersValidator());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ValidStandardEpisodeFormatValidator : PropertyValidator
|
public class ValidStandardEpisodeFormatValidator : PropertyValidator
|
||||||
|
@ -132,6 +140,34 @@ namespace NzbDrone.Core.Organizer
|
||||||
}
|
}
|
||||||
|
|
||||||
var invalidCharacters = InvalidPathChars.Where(i => value!.IndexOf(i) >= 0).ToList();
|
var invalidCharacters = InvalidPathChars.Where(i => value!.IndexOf(i) >= 0).ToList();
|
||||||
|
|
||||||
|
if (invalidCharacters.Any())
|
||||||
|
{
|
||||||
|
context.MessageFormatter.AppendArgument("InvalidCharacters", string.Join("", invalidCharacters));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IllegalColonCharactersValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private static readonly string[] InvalidPathChars = FileNameBuilder.BadCharacters.Concat(new[] { ":" }).ToArray();
|
||||||
|
|
||||||
|
protected override string GetDefaultMessageTemplate() => "Contains illegal characters: {InvalidCharacters}";
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
var value = context.PropertyValue as string;
|
||||||
|
|
||||||
|
if (value.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalidCharacters = InvalidPathChars.Where(i => value!.IndexOf(i, StringComparison.Ordinal) >= 0).ToList();
|
||||||
|
|
||||||
if (invalidCharacters.Any())
|
if (invalidCharacters.Any())
|
||||||
{
|
{
|
||||||
context.MessageFormatter.AppendArgument("InvalidCharacters", string.Join("", invalidCharacters));
|
context.MessageFormatter.AppendArgument("InvalidCharacters", string.Join("", invalidCharacters));
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
RenameEpisodes = false,
|
RenameEpisodes = false,
|
||||||
ReplaceIllegalCharacters = true,
|
ReplaceIllegalCharacters = true,
|
||||||
ColonReplacementFormat = ColonReplacementFormat.Smart,
|
ColonReplacementFormat = ColonReplacementFormat.Smart,
|
||||||
|
CustomColonReplacementFormat = string.Empty,
|
||||||
MultiEpisodeStyle = MultiEpisodeStyle.PrefixedRange,
|
MultiEpisodeStyle = MultiEpisodeStyle.PrefixedRange,
|
||||||
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
||||||
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}",
|
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}",
|
||||||
|
@ -21,6 +22,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
public bool RenameEpisodes { get; set; }
|
public bool RenameEpisodes { get; set; }
|
||||||
public bool ReplaceIllegalCharacters { get; set; }
|
public bool ReplaceIllegalCharacters { get; set; }
|
||||||
public ColonReplacementFormat ColonReplacementFormat { get; set; }
|
public ColonReplacementFormat ColonReplacementFormat { get; set; }
|
||||||
|
public string CustomColonReplacementFormat { get; set; }
|
||||||
public MultiEpisodeStyle MultiEpisodeStyle { get; set; }
|
public MultiEpisodeStyle MultiEpisodeStyle { get; set; }
|
||||||
public string StandardEpisodeFormat { get; set; }
|
public string StandardEpisodeFormat { get; set; }
|
||||||
public string DailyEpisodeFormat { get; set; }
|
public string DailyEpisodeFormat { get; set; }
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace Sonarr.Api.V3.Config
|
||||||
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
|
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
|
||||||
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
|
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
|
||||||
SharedValidator.RuleFor(c => c.SpecialsFolderFormat).ValidSpecialsFolderFormat();
|
SharedValidator.RuleFor(c => c.SpecialsFolderFormat).ValidSpecialsFolderFormat();
|
||||||
|
SharedValidator.RuleFor(c => c.CustomColonReplacementFormat).ValidCustomColonReplacement().When(c => c.ColonReplacementFormat == (int)ColonReplacementFormat.Custom);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override NamingConfigResource GetResourceById(int id)
|
protected override NamingConfigResource GetResourceById(int id)
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace Sonarr.Api.V3.Config
|
||||||
public bool RenameEpisodes { get; set; }
|
public bool RenameEpisodes { get; set; }
|
||||||
public bool ReplaceIllegalCharacters { get; set; }
|
public bool ReplaceIllegalCharacters { get; set; }
|
||||||
public int ColonReplacementFormat { get; set; }
|
public int ColonReplacementFormat { get; set; }
|
||||||
|
public string CustomColonReplacementFormat { get; set; }
|
||||||
public int MultiEpisodeStyle { get; set; }
|
public int MultiEpisodeStyle { get; set; }
|
||||||
public string StandardEpisodeFormat { get; set; }
|
public string StandardEpisodeFormat { get; set; }
|
||||||
public string DailyEpisodeFormat { get; set; }
|
public string DailyEpisodeFormat { get; set; }
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace Sonarr.Api.V3.Config
|
||||||
RenameEpisodes = model.RenameEpisodes,
|
RenameEpisodes = model.RenameEpisodes,
|
||||||
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
||||||
ColonReplacementFormat = (int)model.ColonReplacementFormat,
|
ColonReplacementFormat = (int)model.ColonReplacementFormat,
|
||||||
|
CustomColonReplacementFormat = model.CustomColonReplacementFormat,
|
||||||
MultiEpisodeStyle = (int)model.MultiEpisodeStyle,
|
MultiEpisodeStyle = (int)model.MultiEpisodeStyle,
|
||||||
StandardEpisodeFormat = model.StandardEpisodeFormat,
|
StandardEpisodeFormat = model.StandardEpisodeFormat,
|
||||||
DailyEpisodeFormat = model.DailyEpisodeFormat,
|
DailyEpisodeFormat = model.DailyEpisodeFormat,
|
||||||
|
@ -45,6 +46,7 @@ namespace Sonarr.Api.V3.Config
|
||||||
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
||||||
MultiEpisodeStyle = (MultiEpisodeStyle)resource.MultiEpisodeStyle,
|
MultiEpisodeStyle = (MultiEpisodeStyle)resource.MultiEpisodeStyle,
|
||||||
ColonReplacementFormat = (ColonReplacementFormat)resource.ColonReplacementFormat,
|
ColonReplacementFormat = (ColonReplacementFormat)resource.ColonReplacementFormat,
|
||||||
|
CustomColonReplacementFormat = resource.CustomColonReplacementFormat,
|
||||||
StandardEpisodeFormat = resource.StandardEpisodeFormat,
|
StandardEpisodeFormat = resource.StandardEpisodeFormat,
|
||||||
DailyEpisodeFormat = resource.DailyEpisodeFormat,
|
DailyEpisodeFormat = resource.DailyEpisodeFormat,
|
||||||
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
|
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
|
||||||
|
|
Loading…
Reference in New Issue