using System; using System.Collections.Generic; using System.IO; using System.Linq; using NLog; using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Organizer { public interface IBuildFileNames { string BuildFilename(IList episodes, Series series, EpisodeFile episodeFile); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); } public interface INamingConfigService { NamingConfig GetConfig(); NamingConfig Save(NamingConfig namingConfig); } public class NamingConfigService : INamingConfigService { private readonly IBasicRepository _repository; public NamingConfigService(IBasicRepository repository) { _repository = repository; } public NamingConfig GetConfig() { var config = _repository.SingleOrDefault(); if (config == null) { _repository.Insert(NamingConfig.Default); config = _repository.Single(); } return config; } public NamingConfig Save(NamingConfig namingConfig) { return _repository.Upsert(namingConfig); } } public class FileNameBuilder : IBuildFileNames { private readonly INamingConfigService _namingConfigService; private readonly Logger _logger; public FileNameBuilder(INamingConfigService namingConfigService, Logger logger) { _namingConfigService = namingConfigService; _logger = logger; } public string BuildFilename(IList episodes, Series series, EpisodeFile episodeFile) { var nameSpec = _namingConfigService.GetConfig(); if (nameSpec.UseSceneName) { if (String.IsNullOrWhiteSpace(episodeFile.SceneName)) { return Path.GetFileNameWithoutExtension(episodeFile.Path); } return episodeFile.SceneName; } var sortedEpisodes = episodes.OrderBy(e => e.EpisodeNumber); var numberStyle = GetNumberStyle(nameSpec.NumberStyle); var episodeNames = new List { Parser.Parser.CleanupEpisodeTitle(sortedEpisodes.First().Title) }; var result = String.Empty; if (nameSpec.IncludeSeriesTitle) { result += series.Title + nameSpec.Separator; } if (series.SeriesType == SeriesTypes.Standard) { result += numberStyle.Pattern.Replace("%0e", String.Format("{0:00}", sortedEpisodes.First().EpisodeNumber)); if (episodes.Count > 1) { var multiEpisodeStyle = GetMultiEpisodeStyle(nameSpec.MultiEpisodeStyle); foreach (var episode in sortedEpisodes.Skip(1)) { if (multiEpisodeStyle.Name == "Duplicate") { result += nameSpec.Separator + numberStyle.Pattern; } else { result += multiEpisodeStyle.Pattern; } result = result.Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)); episodeNames.Add(Parser.Parser.CleanupEpisodeTitle(episode.Title)); } } result = result .Replace("%s", String.Format("{0}", episodes.First().SeasonNumber)) .Replace("%0s", String.Format("{0:00}", episodes.First().SeasonNumber)) .Replace("%x", numberStyle.EpisodeSeparator) .Replace("%p", nameSpec.Separator); } else { if (episodes.First().AirDate.HasValue) result += episodes.First().AirDate.Value.ToString("yyyy-MM-dd"); else result += "Unknown"; } if (nameSpec.IncludeEpisodeTitle) { if (episodeNames.Distinct().Count() == 1) result += nameSpec.Separator + episodeNames.First(); else result += nameSpec.Separator + String.Join(" + ", episodeNames.Distinct()); } if (nameSpec.IncludeQuality) { result += String.Format(" [{0}]", episodeFile.Quality.Quality); if (episodeFile.Quality.Proper) result += " [Proper]"; } if (nameSpec.ReplaceSpaces) result = result.Replace(' ', '.'); _logger.Trace("New File Name is: [{0}]", result.Trim()); return CleanFilename(result.Trim()); } public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) { var nameSpec = _namingConfigService.GetConfig(); string path = series.Path; if (series.SeasonFolder) { var seasonFolder = nameSpec.SeasonFolderFormat .Replace("%0s", seasonNumber.ToString("00")) .Replace("%s", seasonNumber.ToString()); path = Path.Combine(path, seasonFolder); } return Path.Combine(path, fileName + extension); } public static string CleanFilename(string name) { string result = name; string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" }; string[] goodCharacters = { "+", "+", "{", "}", "!", "@", "-", "#", "`" }; for (int i = 0; i < badCharacters.Length; i++) result = result.Replace(badCharacters[i], goodCharacters[i]); return result.Trim(); } private static readonly List NumberStyles = new List { new EpisodeSortingType { Id = 0, Name = "1x05", Pattern = "%sx%0e", EpisodeSeparator = "x" }, new EpisodeSortingType { Id = 1, Name = "01x05", Pattern = "%0sx%0e", EpisodeSeparator = "x" }, new EpisodeSortingType { Id = 2, Name = "S01E05", Pattern = "S%0sE%0e", EpisodeSeparator = "E" }, new EpisodeSortingType { Id = 3, Name = "s01e05", Pattern = "s%0se%0e", EpisodeSeparator = "e" } }; private static readonly List MultiEpisodeStyles = new List { new EpisodeSortingType { Id = 0, Name = "Extend", Pattern = "-%0e" }, new EpisodeSortingType { Id = 1, Name = "Duplicate", Pattern = "%p%0s%x%0e" }, new EpisodeSortingType { Id = 2, Name = "Repeat", Pattern = "%x%0e" }, new EpisodeSortingType { Id = 3, Name = "Scene", Pattern = "-%x%0e" } }; private static EpisodeSortingType GetNumberStyle(int id) { return NumberStyles.Single(s => s.Id == id); } private static EpisodeSortingType GetMultiEpisodeStyle(int id) { return MultiEpisodeStyles.Single(s => s.Id == id); } } }