Fixed: Truncating too long filenames with unicode characters

closes #4085
This commit is contained in:
Taloth Saldono 2020-11-14 21:20:03 +01:00
parent 05820ac272
commit 158e31d54a
3 changed files with 58 additions and 11 deletions

View File

@ -141,5 +141,45 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
result.Length.Should().BeLessOrEqualTo(255); result.Length.Should().BeLessOrEqualTo(255);
result.Should().Be("Lorem ipsum dolor sit amet, consectetur adipiscing elit Maecenas et magna sem Morbi vitae volutpat quam, id porta arcu Orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus musu Cras vestibulum - S01E01 - Episode Ti... HDTV-720p"); result.Should().Be("Lorem ipsum dolor sit amet, consectetur adipiscing elit Maecenas et magna sem Morbi vitae volutpat quam, id porta arcu Orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus musu Cras vestibulum - S01E01 - Episode Ti... HDTV-720p");
} }
[Test]
public void should_truncate_titles_measuring_series_title_bytes()
{
_series.Title = "Lor\u00E9m ipsum dolor sit amet, consectetur adipiscing elit Maecenas et magna sem Morbi vitae volutpat quam, id porta arcu Orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus musu Cras vestibulum";
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}";
var result = Subject.BuildFileName(new List<Episode> { _episodes.First() }, _series, _episodeFile);
result.GetByteCount().Should().BeLessOrEqualTo(255);
result.Should().Be("Lor\u00E9m ipsum dolor sit amet, consectetur adipiscing elit Maecenas et magna sem Morbi vitae volutpat quam, id porta arcu Orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus musu Cras vestibulum - S01E01 - Episode T... HDTV-720p");
}
[Test]
public void should_truncate_titles_measuring_episode_title_bytes()
{
_series.Title = "Lorem ipsum dolor sit amet, consectetur adipiscing elit Maecenas et magna sem Morbi vitae volutpat quam, id porta arcu Orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus musu Cras vestibulum";
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}";
_episodes.First().Title = "Episod\u00E9 Title";
var result = Subject.BuildFileName(new List<Episode> { _episodes.First() }, _series, _episodeFile);
result.GetByteCount().Should().BeLessOrEqualTo(255);
result.Should().Be("Lorem ipsum dolor sit amet, consectetur adipiscing elit Maecenas et magna sem Morbi vitae volutpat quam, id porta arcu Orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus musu Cras vestibulum - S01E01 - Episod\u00E9 T... HDTV-720p");
}
[Test]
public void should_truncate_titles_measuring_episode_title_bytes_middle()
{
_series.Title = "Lorem ipsum dolor sit amet, consectetur adipiscing elit Maecenas et magna sem Morbi vitae volutpat quam, id porta arcu Orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus musu Cras vestibulum";
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}";
_episodes.First().Title = "Episode T\u00E9tle";
var result = Subject.BuildFileName(new List<Episode> { _episodes.First() }, _series, _episodeFile);
result.GetByteCount().Should().BeLessOrEqualTo(255);
result.Should().Be("Lorem ipsum dolor sit amet, consectetur adipiscing elit Maecenas et magna sem Morbi vitae volutpat quam, id porta arcu Orci varius natoque penatibus et magnis dis parturient montes nascetur ridiculus musu Cras vestibulum - S01E01 - Episode T... HDTV-720p");
}
} }
} }

View File

@ -76,6 +76,11 @@ namespace NzbDrone.Core
return intList.Max(); return intList.Max();
} }
public static int GetByteCount(this string input)
{
return Encoding.UTF8.GetByteCount(input);
}
public static string Truncate(this string s, int maxLength) public static string Truncate(this string s, int maxLength)
{ {
if (Encoding.UTF8.GetByteCount(s) <= maxLength) if (Encoding.UTF8.GetByteCount(s) <= maxLength)

View File

@ -163,10 +163,10 @@ namespace NzbDrone.Core.Organizer
AddPreferredWords(tokenHandlers, series, episodeFile, preferredWords); AddPreferredWords(tokenHandlers, series, episodeFile, preferredWords);
var component = ReplaceTokens(splitPattern, tokenHandlers, namingConfig, true).Trim(); var component = ReplaceTokens(splitPattern, tokenHandlers, namingConfig, true).Trim();
var maxPathSegmentLength = LongPathSupport.MaxFileNameLength; var maxPathSegmentLength = Math.Min(LongPathSupport.MaxFileNameLength, maxPath);
if (i == splitPatterns.Length - 1) if (i == splitPatterns.Length - 1)
{ {
maxPathSegmentLength -= extension.Length; maxPathSegmentLength -= extension.GetByteCount();
} }
var maxEpisodeTitleLength = maxPathSegmentLength - GetLengthWithoutEpisodeTitle(component, namingConfig); var maxEpisodeTitleLength = maxPathSegmentLength - GetLengthWithoutEpisodeTitle(component, namingConfig);
@ -193,7 +193,7 @@ namespace NzbDrone.Core.Organizer
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
var seasonPath = BuildSeasonPath(series, episodes.First().SeasonNumber); var seasonPath = BuildSeasonPath(series, episodes.First().SeasonNumber);
var remainingPathLength = LongPathSupport.MaxFilePathLength - seasonPath.Length - 1; var remainingPathLength = LongPathSupport.MaxFilePathLength - seasonPath.GetByteCount() - 1;
var fileName = BuildFileName(episodes, series, episodeFile, extension, remainingPathLength, namingConfig, preferredWords); var fileName = BuildFileName(episodes, series, episodeFile, extension, remainingPathLength, namingConfig, preferredWords);
return Path.Combine(seasonPath, fileName); return Path.Combine(seasonPath, fileName);
@ -935,33 +935,35 @@ namespace NzbDrone.Core.Organizer
var joined = string.Join(separator, titles); var joined = string.Join(separator, titles);
if (joined.Length <= maxLength) if (joined.GetByteCount() <= maxLength)
{ {
return joined; return joined;
} }
var firstTitle = titles.First(); var firstTitle = titles.First();
var firstTitleLength = firstTitle.GetByteCount();
if (titles.Count >= 2) if (titles.Count >= 2)
{ {
var lastTitle = titles.Last(); var lastTitle = titles.Last();
if (firstTitle.Length + lastTitle.Length + 3 <= maxLength) var lastTitleLength = lastTitle.GetByteCount();
if (firstTitleLength + lastTitleLength + 3 <= maxLength)
{ {
return $"{firstTitle.Trim(' ', '.')}{{ellipsis}}{lastTitle}"; return $"{firstTitle.TrimEnd(' ', '.')}{{ellipsis}}{lastTitle}";
} }
} }
if (titles.Count > 1 && firstTitle.Length + 3 <= maxLength) if (titles.Count > 1 && firstTitleLength + 3 <= maxLength)
{ {
return $"{firstTitle.Trim(' ', '.')}{{ellipsis}}"; return $"{firstTitle.TrimEnd(' ', '.')}{{ellipsis}}";
} }
if (titles.Count == 1 && firstTitle.Length <= maxLength) if (titles.Count == 1 && firstTitleLength <= maxLength)
{ {
return firstTitle; return firstTitle;
} }
return $"{firstTitle.Substring(0, maxLength - 3).Trim(' ', '.')}{{ellipsis}}"; return $"{firstTitle.Truncate(maxLength - 3).TrimEnd(' ', '.')}{{ellipsis}}";
} }
private string CleanupEpisodeTitle(string title) private string CleanupEpisodeTitle(string title)
@ -1023,7 +1025,7 @@ namespace NzbDrone.Core.Organizer
var result = ReplaceTokens(pattern, tokenHandlers, namingConfig); var result = ReplaceTokens(pattern, tokenHandlers, namingConfig);
return result.Length; return result.GetByteCount();
} }
} }