From a29e1eb036d22d108ee4234d31650a061b591073 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 21 Jan 2014 11:24:12 -0500 Subject: [PATCH] #680 - begin resolution feature --- .../Configuration/ServerConfiguration.cs | 11 +-- .../FileOrganizationResult.cs | 21 ++++- .../FileOrganizationService.cs | 2 +- .../FileOrganization/TvFileSorter.cs | 83 ++++++++++--------- .../SqliteFileOrganizationRepository.cs | 29 ++++++- 5 files changed, 93 insertions(+), 53 deletions(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index dd0c0ce3ca..f017fdf16f 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -328,16 +328,12 @@ namespace MediaBrowser.Model.Configuration public string SeasonZeroFolderName { get; set; } public string EpisodeNamePattern { get; set; } - + public string MultiEpisodeNamePattern { get; set; } + public bool OverwriteExistingEpisodes { get; set; } public bool DeleteEmptyFolders { get; set; } - /// - /// Will log results but will not actually make any changes - /// - public bool EnableTrialMode { get; set; } - public TvFileOrganizationOptions() { MinFileSizeMb = 50; @@ -347,10 +343,9 @@ namespace MediaBrowser.Model.Configuration WatchLocations = new string[] { }; EpisodeNamePattern = "%sn - %sx%0e - %en.%ext"; + MultiEpisodeNamePattern = "%sn - %sx%0e-x%0ed - %en.%ext"; SeasonFolderPattern = "Season %s"; SeasonZeroFolderName = "Season 0"; - - EnableTrialMode = true; } } } diff --git a/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs b/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs index ca912ed634..d2d52f4aa6 100644 --- a/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs +++ b/MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs @@ -33,6 +33,24 @@ namespace MediaBrowser.Model.FileOrganization /// /// The extracted year. public int? ExtractedYear { get; set; } + + /// + /// Gets or sets the extracted season number. + /// + /// The extracted season number. + public int? ExtractedSeasonNumber { get; set; } + + /// + /// Gets or sets the extracted episode number. + /// + /// The extracted episode number. + public int? ExtractedEpisodeNumber { get; set; } + + /// + /// Gets or sets the extracted ending episode number. + /// + /// The extracted ending episode number. + public int? ExtractedEndingEpisodeNumber { get; set; } /// /// Gets or sets the target path. @@ -69,8 +87,7 @@ namespace MediaBrowser.Model.FileOrganization { Success, Failure, - SkippedExisting, - SkippedTrial + SkippedExisting } public enum FileOrganizerType diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs index 49037ef96d..277b26415f 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization throw new ArgumentNullException("result"); } - result.Id = (result.OriginalPath + (result.TargetPath ?? string.Empty)).GetMD5().ToString("N"); + result.Id = result.OriginalPath.GetMD5().ToString("N"); return _repo.SaveResult(result, cancellationToken); } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/TvFileSorter.cs b/MediaBrowser.Server.Implementations/FileOrganization/TvFileSorter.cs index 5aaad2ad97..3e653fae41 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/TvFileSorter.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/TvFileSorter.cs @@ -67,7 +67,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { var result = await SortFile(file.FullName, options, allSeries).ConfigureAwait(false); - if (result.Status == FileSortingStatus.Success && !options.EnableTrialMode) + if (result.Status == FileSortingStatus.Success) { scanLibrary = true; } @@ -83,19 +83,16 @@ namespace MediaBrowser.Server.Implementations.FileOrganization cancellationToken.ThrowIfCancellationRequested(); progress.Report(99); - if (!options.EnableTrialMode) + foreach (var path in watchLocations) { - foreach (var path in watchLocations) + if (options.LeftOverFileExtensionsToDelete.Length > 0) { - if (options.LeftOverFileExtensionsToDelete.Length > 0) - { - DeleteLeftOverFiles(path, options.LeftOverFileExtensionsToDelete); - } + DeleteLeftOverFiles(path, options.LeftOverFileExtensionsToDelete); + } - if (options.DeleteEmptyFolders) - { - DeleteEmptyFolders(path); - } + if (options.DeleteEmptyFolders) + { + DeleteEmptyFolders(path); } } @@ -153,16 +150,24 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { var season = TVUtils.GetSeasonNumberFromEpisodeFile(path); + result.ExtractedSeasonNumber = season; + if (season.HasValue) { // Passing in true will include a few extra regex's var episode = TVUtils.GetEpisodeNumberFromFile(path, true); + result.ExtractedEpisodeNumber = episode; + if (episode.HasValue) { _logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, season, episode); - SortFile(path, seriesName, season.Value, episode.Value, options, allSeries, result); + var endingEpisodeNumber = TVUtils.GetEndingEpisodeNumberFromFile(path); + + result.ExtractedEndingEpisodeNumber = endingEpisodeNumber; + + SortFile(path, seriesName, season.Value, episode.Value, endingEpisodeNumber, options, allSeries, result); } else { @@ -200,10 +205,11 @@ namespace MediaBrowser.Server.Implementations.FileOrganization /// Name of the series. /// The season number. /// The episode number. + /// The ending epiosde number. /// The options. /// All series. /// The result. - private void SortFile(string path, string seriesName, int seasonNumber, int episodeNumber, TvFileOrganizationOptions options, IEnumerable allSeries, FileOrganizationResult result) + private void SortFile(string path, string seriesName, int seasonNumber, int episodeNumber, int? endingEpiosdeNumber, TvFileOrganizationOptions options, IEnumerable allSeries, FileOrganizationResult result) { var series = GetMatchingSeries(seriesName, allSeries, result); @@ -219,7 +225,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization _logger.Info("Sorting file {0} into series {1}", path, series.Path); // Proceed to sort the file - var newPath = GetNewPath(path, series, seasonNumber, episodeNumber, options); + var newPath = GetNewPath(path, series, seasonNumber, episodeNumber, endingEpiosdeNumber, options); if (string.IsNullOrEmpty(newPath)) { @@ -233,12 +239,6 @@ namespace MediaBrowser.Server.Implementations.FileOrganization _logger.Info("Sorting file {0} to new path {1}", path, newPath); result.TargetPath = newPath; - if (options.EnableTrialMode) - { - result.Status = FileSortingStatus.SkippedTrial; - return; - } - var targetExists = File.Exists(result.TargetPath); if (!options.OverwriteExistingEpisodes && targetExists) { @@ -315,12 +315,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization /// The series. /// The season number. /// The episode number. + /// The ending episode number. /// The options. /// System.String. - private string GetNewPath(string sourcePath, Series series, int seasonNumber, int episodeNumber, TvFileOrganizationOptions options) + private string GetNewPath(string sourcePath, Series series, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, TvFileOrganizationOptions options) { + // If season and episode numbers match var currentEpisodes = series.RecursiveChildren.OfType() - .Where(i => i.IndexNumber.HasValue && i.IndexNumber.Value == episodeNumber && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber) + .Where(i => i.IndexNumber.HasValue && + i.IndexNumber.Value == episodeNumber && + i.ParentIndexNumber.HasValue && + i.ParentIndexNumber.Value == seasonNumber) .ToList(); if (currentEpisodes.Count == 0) @@ -328,33 +333,27 @@ namespace MediaBrowser.Server.Implementations.FileOrganization return null; } - var newPath = currentEpisodes - .Where(i => i.LocationType == LocationType.FileSystem) - .Select(i => i.Path) - .FirstOrDefault(); + var newPath = GetSeasonFolderPath(series, seasonNumber, options); - if (string.IsNullOrEmpty(newPath)) - { - newPath = GetSeasonFolderPath(series, seasonNumber, options); + var episode = currentEpisodes.First(); - var episode = currentEpisodes.First(); + var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber, episodeNumber, endingEpisodeNumber, episode.Name, options); - var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber, episodeNumber, episode.Name, options); - - newPath = Path.Combine(newPath, episodeFileName); - } + newPath = Path.Combine(newPath, episodeFileName); return newPath; } - private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, string episodeTitle, TvFileOrganizationOptions options) + private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options) { seriesName = _fileSystem.GetValidFilename(seriesName); episodeTitle = _fileSystem.GetValidFilename(episodeTitle); var sourceExtension = (Path.GetExtension(sourcePath) ?? string.Empty).TrimStart('.'); - return options.EpisodeNamePattern.Replace("%sn", seriesName) + var pattern = endingEpisodeNumber.HasValue ? options.MultiEpisodeNamePattern : options.EpisodeNamePattern; + + var result = pattern.Replace("%sn", seriesName) .Replace("%s.n", seriesName.Replace(" ", ".")) .Replace("%s_n", seriesName.Replace(" ", "_")) .Replace("%s", seasonNumber.ToString(UsCulture)) @@ -363,8 +362,16 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .Replace("%ext", sourceExtension) .Replace("%en", episodeTitle) .Replace("%e.n", episodeTitle.Replace(" ", ".")) - .Replace("%e_n", episodeTitle.Replace(" ", "_")) - .Replace("%e", episodeNumber.ToString(UsCulture)) + .Replace("%e_n", episodeTitle.Replace(" ", "_")); + + if (endingEpisodeNumber.HasValue) + { + result = result.Replace("%ed", endingEpisodeNumber.Value.ToString(UsCulture)) + .Replace("%0ed", endingEpisodeNumber.Value.ToString("00", UsCulture)) + .Replace("%00ed", endingEpisodeNumber.Value.ToString("000", UsCulture)); + } + + return result.Replace("%e", episodeNumber.ToString(UsCulture)) .Replace("%0e", episodeNumber.ToString("00", UsCulture)) .Replace("%00e", episodeNumber.ToString("000", UsCulture)); } diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs index 75de131944..e20139e086 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.Persistence string[] queries = { - "create table if not exists organizationresults (ResultId GUID PRIMARY KEY, OriginalPath TEXT, TargetPath TEXT, OrganizationDate datetime, Status TEXT, OrganizationType TEXT, StatusMessage TEXT, ExtractedName TEXT, ExtractedYear int null)", + "create table if not exists organizationresults (ResultId GUID PRIMARY KEY, OriginalPath TEXT, TargetPath TEXT, OrganizationDate datetime, Status TEXT, OrganizationType TEXT, StatusMessage TEXT, ExtractedName TEXT, ExtractedYear int null, ExtractedSeasonNumber int null, ExtractedEpisodeNumber int null, ExtractedEndingEpisodeNumber int null)", "create index if not exists idx_organizationresults on organizationresults(ResultId)", //pragmas @@ -66,7 +66,7 @@ namespace MediaBrowser.Server.Implementations.Persistence private void PrepareStatements() { _saveResultCommand = _connection.CreateCommand(); - _saveResultCommand.CommandText = "replace into organizationresults (ResultId, OriginalPath, TargetPath, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear) values (@ResultId, @OriginalPath, @TargetPath, @OrganizationDate, @Status, @OrganizationType, @StatusMessage, @ExtractedName, @ExtractedYear)"; + _saveResultCommand.CommandText = "replace into organizationresults (ResultId, OriginalPath, TargetPath, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber) values (@ResultId, @OriginalPath, @TargetPath, @OrganizationDate, @Status, @OrganizationType, @StatusMessage, @ExtractedName, @ExtractedYear, @ExtractedSeasonNumber, @ExtractedEpisodeNumber, @ExtractedEndingEpisodeNumber)"; _saveResultCommand.Parameters.Add(_saveResultCommand, "@ResultId"); _saveResultCommand.Parameters.Add(_saveResultCommand, "@OriginalPath"); @@ -77,6 +77,9 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveResultCommand.Parameters.Add(_saveResultCommand, "@StatusMessage"); _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedName"); _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedYear"); + _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedSeasonNumber"); + _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedEpisodeNumber"); + _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedEndingEpisodeNumber"); _deleteResultCommand = _connection.CreateCommand(); _deleteResultCommand.CommandText = "delete from organizationresults where ResultId = @ResultId"; @@ -110,6 +113,9 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveResultCommand.GetParameter(6).Value = result.StatusMessage; _saveResultCommand.GetParameter(7).Value = result.ExtractedName; _saveResultCommand.GetParameter(8).Value = result.ExtractedYear; + _saveResultCommand.GetParameter(9).Value = result.ExtractedSeasonNumber; + _saveResultCommand.GetParameter(10).Value = result.ExtractedEpisodeNumber; + _saveResultCommand.GetParameter(11).Value = result.ExtractedEndingEpisodeNumber; _saveResultCommand.Transaction = transaction; @@ -211,7 +217,7 @@ namespace MediaBrowser.Server.Implementations.Persistence using (var cmd = _connection.CreateCommand()) { - cmd.CommandText = "SELECT ResultId, OriginalPath, TargetPath, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear from organizationresults"; + cmd.CommandText = "SELECT ResultId, OriginalPath, TargetPath, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber from organizationresults"; if (query.StartIndex.HasValue && query.StartIndex.Value > 0) { @@ -263,7 +269,7 @@ namespace MediaBrowser.Server.Implementations.Persistence using (var cmd = _connection.CreateCommand()) { - cmd.CommandText = "select ResultId, OriginalPath, TargetPath, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear from organizationresults where ResultId=@Id"; + cmd.CommandText = "select ResultId, OriginalPath, TargetPath, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber from organizationresults where ResultId=@Id"; cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; @@ -317,6 +323,21 @@ namespace MediaBrowser.Server.Implementations.Persistence result.ExtractedYear = reader.GetInt32(8); } + if (!reader.IsDBNull(9)) + { + result.ExtractedSeasonNumber = reader.GetInt32(9); + } + + if (!reader.IsDBNull(10)) + { + result.ExtractedEpisodeNumber = reader.GetInt32(10); + } + + if (!reader.IsDBNull(11)) + { + result.ExtractedEndingEpisodeNumber = reader.GetInt32(11); + } + return result; }