From f7636a6b29996984f5aa4c72b89a7fb233621f6f Mon Sep 17 00:00:00 2001 From: softworkz Date: Mon, 21 Sep 2015 15:24:33 +0200 Subject: [PATCH 1/2] Auto-Organize: Fix PathTooLongException due to long EpisodeTitle Especially with Multi-Episodes, the EpisodeTitle can become quite long, since all comprised titles get concatenated into it. By default, the EpisodeTitle is included in the single and multi-episode naming patterns and this in turn can quickly lead to a PathTooLongException. This fix tries to keep as much as possible from the title string while keeping the final path within the allowed limit. --- .../FileOrganization/EpisodeFileOrganizer.cs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index cdac598fda..aa553801fd 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -435,7 +435,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var newPath = GetSeasonFolderPath(series, seasonNumber, options); - var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber, episodeNumber, endingEpisodeNumber, episode.Name, options); + var episodeFileName = GetEpisodeFileName(newPath.Length, sourcePath, series.Name, seasonNumber, episodeNumber, endingEpisodeNumber, episode.Name, options); newPath = Path.Combine(newPath, episodeFileName); @@ -492,7 +492,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName)); } - private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options) + private string GetEpisodeFileName(int destPathLength, string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options) { seriesName = _fileSystem.GetValidFilename(seriesName).Trim(); episodeTitle = _fileSystem.GetValidFilename(episodeTitle).Trim(); @@ -508,9 +508,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .Replace("%0s", seasonNumber.ToString("00", _usCulture)) .Replace("%00s", seasonNumber.ToString("000", _usCulture)) .Replace("%ext", sourceExtension) - .Replace("%en", episodeTitle) - .Replace("%e.n", episodeTitle.Replace(" ", ".")) - .Replace("%e_n", episodeTitle.Replace(" ", "_")); + .Replace("%en", "%#1") + .Replace("%e.n", "%#2") + .Replace("%e_n", "%#3"); if (endingEpisodeNumber.HasValue) { @@ -519,9 +519,29 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .Replace("%00ed", endingEpisodeNumber.Value.ToString("000", _usCulture)); } - return result.Replace("%e", episodeNumber.ToString(_usCulture)) + result = result.Replace("%e", episodeNumber.ToString(_usCulture)) .Replace("%0e", episodeNumber.ToString("00", _usCulture)) .Replace("%00e", episodeNumber.ToString("000", _usCulture)); + + // Add +1 because it is unsure if destpathlength includes a trailing backslash + int currentTotalLength = destPathLength + 1 + result.Length; + + // MAX_PATH - trailing charachter - drive component: 260 - 1 - 3 = 256 + // Usually maxRemainingTitleLength would include the drive component, but use 256 to be sure + // Substract 3 for the temp token length (%#1, %#2 or %#3) + int maxRemainingTitleLength = 256 - currentTotalLength + 3; + string shortenedEpisodeTitle = string.Empty; + + if (maxRemainingTitleLength > 5) + { + // A title with fewer than 5 letters wouldn't be of much value + shortenedEpisodeTitle = episodeTitle.Substring(0, Math.Min(maxRemainingTitleLength, episodeTitle.Length)); + } + + return result.Replace("%#1", shortenedEpisodeTitle) + .Replace("%#2", shortenedEpisodeTitle.Replace(" ", ".")) + .Replace("%#3", shortenedEpisodeTitle.Replace(" ", "_")); + } private bool IsSameEpisode(string sourcePath, string newPath) From 4a235bd4bdbff97b703d3e1f39c0ae31ed441470 Mon Sep 17 00:00:00 2001 From: softworkz Date: Fri, 2 Oct 2015 20:44:30 +0200 Subject: [PATCH 2/2] Auto-Organize: Fix PathTooLongException due to long EpisodeTitle #2 --- .../FileOrganization/EpisodeFileOrganizer.cs | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index aa553801fd..dc5b070220 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -435,21 +435,29 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var newPath = GetSeasonFolderPath(series, seasonNumber, options); - var episodeFileName = GetEpisodeFileName(newPath.Length, sourcePath, series.Name, seasonNumber, episodeNumber, endingEpisodeNumber, episode.Name, options); - - newPath = Path.Combine(newPath, episodeFileName); + // MAX_PATH - trailing charachter - drive component: 260 - 1 - 3 = 256 + // Usually newPath would include the drive component, but use 256 to be sure + var maxFilenameLength = 256 - newPath.Length; - // Try to account for windows limitations by removing the episode title - if (newPath.Length > 255) + if (!newPath.EndsWith(@"\")) { - var extension = Path.GetExtension(episodeFileName); - var fileName = Path.GetFileNameWithoutExtension(episodeFileName); - fileName = fileName.Replace(episode.Name, string.Empty, StringComparison.OrdinalIgnoreCase); - episodeFileName = Path.ChangeExtension(fileName, extension); + // Remove 1 for missing backslash combining path and filename + maxFilenameLength--; + } + + // Remove additional 4 chars to prevent PathTooLongException for downloaded subtitles (eg. filename.ext.eng.srt) + maxFilenameLength -= 4; - newPath = Path.Combine(newPath, episodeFileName); + var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber, episodeNumber, endingEpisodeNumber, episode.Name, options, maxFilenameLength); + + if (string.IsNullOrEmpty(episodeFileName)) + { + // cause failure + return string.Empty; } + newPath = Path.Combine(newPath, episodeFileName); + return newPath; } @@ -492,7 +500,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName)); } - private string GetEpisodeFileName(int destPathLength, string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options) + private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options, int? maxLength) { seriesName = _fileSystem.GetValidFilename(seriesName).Trim(); episodeTitle = _fileSystem.GetValidFilename(episodeTitle).Trim(); @@ -523,25 +531,33 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .Replace("%0e", episodeNumber.ToString("00", _usCulture)) .Replace("%00e", episodeNumber.ToString("000", _usCulture)); - // Add +1 because it is unsure if destpathlength includes a trailing backslash - int currentTotalLength = destPathLength + 1 + result.Length; - - // MAX_PATH - trailing charachter - drive component: 260 - 1 - 3 = 256 - // Usually maxRemainingTitleLength would include the drive component, but use 256 to be sure - // Substract 3 for the temp token length (%#1, %#2 or %#3) - int maxRemainingTitleLength = 256 - currentTotalLength + 3; - string shortenedEpisodeTitle = string.Empty; - - if (maxRemainingTitleLength > 5) + if (maxLength.HasValue && result.Contains("%#")) { - // A title with fewer than 5 letters wouldn't be of much value - shortenedEpisodeTitle = episodeTitle.Substring(0, Math.Min(maxRemainingTitleLength, episodeTitle.Length)); + // Substract 3 for the temp token length (%#1, %#2 or %#3) + int maxRemainingTitleLength = maxLength.Value - result.Length + 3; + string shortenedEpisodeTitle = string.Empty; + + if (maxRemainingTitleLength > 5) + { + // A title with fewer than 5 letters wouldn't be of much value + shortenedEpisodeTitle = episodeTitle.Substring(0, Math.Min(maxRemainingTitleLength, episodeTitle.Length)); + } + + result = result.Replace("%#1", shortenedEpisodeTitle) + .Replace("%#2", shortenedEpisodeTitle.Replace(" ", ".")) + .Replace("%#3", shortenedEpisodeTitle.Replace(" ", "_")); } - return result.Replace("%#1", shortenedEpisodeTitle) - .Replace("%#2", shortenedEpisodeTitle.Replace(" ", ".")) - .Replace("%#3", shortenedEpisodeTitle.Replace(" ", "_")); + if (maxLength.HasValue && result.Length > maxLength.Value) + { + // There may be cases where reducing the title length may still not be sufficient to + // stay below maxLength + var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength); + _logger.Warn(msg); + return string.Empty; + } + return result; } private bool IsSameEpisode(string sourcePath, string newPath)