diff --git a/frontend/src/Settings/MediaManagement/Naming/Naming.tsx b/frontend/src/Settings/MediaManagement/Naming/Naming.tsx index 0a09fc4d0..939716531 100644 --- a/frontend/src/Settings/MediaManagement/Naming/Naming.tsx +++ b/frontend/src/Settings/MediaManagement/Naming/Naming.tsx @@ -248,6 +248,7 @@ function Naming() { translate('MovieFolderFormatHelpText'), ...movieFolderFormatHelpTexts, ]} + helpTextWarning={translate('MovieFolderFormatHelpTextWarning')} errors={[ ...movieFolderFormatErrors, ...settings.movieFolderFormat.errors, diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 70cb30ee3..a8b7cf1eb 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1018,6 +1018,7 @@ "MovieFilesTotaling": "Movie Files Totaling", "MovieFolderFormat": "Movie Folder Format", "MovieFolderFormatHelpText": "Used when adding a new movie or moving movies via the movie editor", + "MovieFolderFormatHelpTextWarning": "Folder tokens associated with movie file properties have been deprecated and will no longer be supported in the next major version.", "MovieFolderImportedTooltip": "Movie imported from movie folder", "MovieFootNote": "Optionally control truncation to a maximum number of bytes including ellipsis (`...`). Truncating from the end (e.g. `{Movie Title:30}`) or the beginning (e.g. `{Movie Title:-30}`) are both supported.", "MovieGrabbedTooltip": "Movie grabbed from {indexer} and sent to {downloadClient}", diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 1e865b543..2c7bf92b1 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -56,6 +56,9 @@ namespace NzbDrone.Core.Organizer private static readonly Regex ReservedDeviceNamesRegex = new Regex(@"^(?:aux|com[1-9]|con|lpt[1-9]|nul|prn)\.", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static readonly Regex DeprecatedMovieFolderTokensRegex = new Regex(@"(\{(?:Original[- ._](?:Title|Filename)|Release[- ._]Group|Edition[- ._]Tags|Quality[- ._](?:Full|Title|Proper|Real)|MediaInfo[- ._](?:Video|VideoCodec|VideoBitDepth|Audio|AudioCodec|AudioChannels|AudioLanguages|AudioLanguagesAll|SubtitleLanguages|SubtitleLanguagesAll|3D|Simple|Full|VideoDynamicRange|VideoDynamicRangeType))\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + // generated from https://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt public static readonly ImmutableDictionary Iso639BTMap = new Dictionary { @@ -171,16 +174,25 @@ namespace NzbDrone.Core.Organizer namingConfig = _namingConfigService.GetConfig(); } - var movieFile = movie.MovieFile; - var pattern = namingConfig.MovieFolderFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + var deprecatedTokensMatch = DeprecatedMovieFolderTokensRegex.Matches(pattern); + + if (deprecatedTokensMatch.Any()) + { + _logger.Warn("DEPRECATED: The use of tokens associated with movie file properties ({0}) in Movie Folder Format is deprecated and will no longer be supported in the next major version. Please update your Movie Folder Format: '{1}'.", string.Join(", ", deprecatedTokensMatch.Select(c => c.Value).ToArray()), pattern); + } + var multipleTokens = TitleRegex.Matches(pattern).Count > 1; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + AddMovieTokens(tokenHandlers, movie); AddReleaseDateTokens(tokenHandlers, movie.Year); AddIdTokens(tokenHandlers, movie); + var movieFile = movie.MovieFile; + if (movie.MovieFile != null) { AddQualityTokens(tokenHandlers, movie, movieFile); diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs index fe49d9f31..227f61199 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Core.Organizer { ruleBuilder.SetValidator(new NotEmptyValidator(null)); ruleBuilder.SetValidator(new IllegalCharactersValidator()); + ruleBuilder.SetValidator(new IllegalMovieFolderTokensValidator()); return ruleBuilder.SetValidator(new ValidMovieFolderFormatValidator()); } @@ -62,6 +63,30 @@ namespace NzbDrone.Core.Organizer } } + public class IllegalMovieFolderTokensValidator : PropertyValidator + { + protected override string GetDefaultMessageTemplate() => "Must not contain deprecated tokens derived from file properties: {tokens}"; + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue is not string value) + { + return false; + } + + var match = FileNameBuilder.DeprecatedMovieFolderTokensRegex.Matches(value); + + if (match.Any()) + { + context.MessageFormatter.AppendArgument("tokens", string.Join(", ", match.Select(c => c.Value).ToArray())); + + return false; + } + + return true; + } + } + public class IllegalCharactersValidator : PropertyValidator { private static readonly char[] InvalidPathChars = Path.GetInvalidPathChars();