diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations/FileSorting')
| -rw-r--r-- | MediaBrowser.Server.Implementations/FileSorting/SortingScheduledTask.cs | 96 | ||||
| -rw-r--r-- | MediaBrowser.Server.Implementations/FileSorting/TvFileSorter.cs | 186 |
2 files changed, 282 insertions, 0 deletions
diff --git a/MediaBrowser.Server.Implementations/FileSorting/SortingScheduledTask.cs b/MediaBrowser.Server.Implementations/FileSorting/SortingScheduledTask.cs new file mode 100644 index 000000000..201e282c0 --- /dev/null +++ b/MediaBrowser.Server.Implementations/FileSorting/SortingScheduledTask.cs @@ -0,0 +1,96 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.FileSorting +{ + public class SortingScheduledTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly IServerConfigurationManager _config; + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + + public SortingScheduledTask(IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager) + { + _config = config; + _logger = logger; + _libraryManager = libraryManager; + } + + public string Name + { + get { return "Sort new files"; } + } + + public string Description + { + get { return "Processes new files available in the configured sorting location."; } + } + + public string Category + { + get { return "Library"; } + } + + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + return Task.Run(() => SortFiles(cancellationToken, progress), cancellationToken); + } + + private void SortFiles(CancellationToken cancellationToken, IProgress<double> progress) + { + var numComplete = 0; + + var paths = _config.Configuration.FileSortingOptions.TvWatchLocations.ToList(); + + foreach (var path in paths) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + SortFiles(path); + } + catch (Exception ex) + { + _logger.ErrorException("Error sorting files from {0}", ex, path); + } + + numComplete++; + double percent = numComplete; + percent /= paths.Count; + + progress.Report(100 * percent); + } + } + + private void SortFiles(string path) + { + new TvFileSorter(_libraryManager, _logger).Sort(path, _config.Configuration.FileSortingOptions); + } + + public IEnumerable<ITaskTrigger> GetDefaultTriggers() + { + return new ITaskTrigger[] + { + new IntervalTrigger{ Interval = TimeSpan.FromMinutes(5)} + }; + } + + public bool IsHidden + { + get { return !_config.Configuration.FileSortingOptions.IsEnabled; } + } + + public bool IsEnabled + { + get { return _config.Configuration.FileSortingOptions.IsEnabled; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/FileSorting/TvFileSorter.cs b/MediaBrowser.Server.Implementations/FileSorting/TvFileSorter.cs new file mode 100644 index 000000000..e2a967ef3 --- /dev/null +++ b/MediaBrowser.Server.Implementations/FileSorting/TvFileSorter.cs @@ -0,0 +1,186 @@ +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.FileSorting +{ + public class TvFileSorter + { + private readonly ILibraryManager _libraryManager; + private readonly ILogger _logger; + + public TvFileSorter(ILibraryManager libraryManager, ILogger logger) + { + _libraryManager = libraryManager; + _logger = logger; + } + + public void Sort(string path, FileSortingOptions options) + { + var minFileBytes = options.MinFileSizeMb * 1024 * 1024; + + var allSeries = _libraryManager.RootFolder + .RecursiveChildren.OfType<Series>() + .Where(i => i.LocationType == LocationType.FileSystem) + .ToList(); + + var eligibleFiles = new DirectoryInfo(path) + .EnumerateFiles("*", SearchOption.AllDirectories) + .Where(i => EntityResolutionHelper.IsVideoFile(i.FullName) && i.Length >= minFileBytes) + .ToList(); + + foreach (var file in eligibleFiles) + { + SortFile(file.FullName, options, allSeries); + } + + if (options.LeftOverFileExtensionsToDelete.Length > 0) + { + DeleteLeftOverFiles(path, options.LeftOverFileExtensionsToDelete); + } + + if (options.DeleteEmptyFolders) + { + DeleteEmptyFolders(path); + } + } + + private void SortFile(string path, FileSortingOptions options, IEnumerable<Series> allSeries) + { + _logger.Info("Sorting file {0}", path); + + var seriesName = TVUtils.GetSeriesNameFromEpisodeFile(path); + + if (!string.IsNullOrEmpty(seriesName)) + { + var season = TVUtils.GetSeasonNumberFromEpisodeFile(path); + + if (season.HasValue) + { + // Passing in true will include a few extra regex's + var episode = TVUtils.GetEpisodeNumberFromFile(path, true); + + 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); + } + else + { + _logger.Warn("Unable to determine episode number from {0}", path); + } + } + else + { + _logger.Warn("Unable to determine season number from {0}", path); + } + } + else + { + _logger.Warn("Unable to determine series name from {0}", path); + } + } + + private void SortFile(string path, string seriesName, int seasonNumber, int episodeNumber, FileSortingOptions options, IEnumerable<Series> allSeries) + { + var series = GetMatchingSeries(seriesName, allSeries); + + if (series == null) + { + _logger.Warn("Unable to find series in library matching name {0}", seriesName); + return; + } + + _logger.Info("Sorting file {0} into series {1}", path, series.Path); + + // Proceed to sort the file + } + + private Series GetMatchingSeries(string seriesName, IEnumerable<Series> allSeries) + { + int? yearInName; + var nameWithoutYear = seriesName; + NameParser.ParseName(nameWithoutYear, out nameWithoutYear, out yearInName); + + return allSeries.Select(i => GetMatchScore(nameWithoutYear, yearInName, i)) + .Where(i => i.Item2 > 0) + .OrderByDescending(i => i.Item2) + .Select(i => i.Item1) + .FirstOrDefault(); + } + + private Tuple<Series, int> GetMatchScore(string sortedName, int? year, Series series) + { + var score = 0; + + if (year.HasValue) + { + if (series.ProductionYear.HasValue && year.Value == series.ProductionYear.Value) + { + score++; + } + } + + // TODO: Improve this + if (string.Equals(sortedName, series.Name, StringComparison.OrdinalIgnoreCase)) + { + score++; + } + + return new Tuple<Series, int>(series, score); + } + + private void DeleteLeftOverFiles(string path, IEnumerable<string> extensions) + { + var eligibleFiles = new DirectoryInfo(path) + .EnumerateFiles("*", SearchOption.AllDirectories) + .Where(i => extensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase)) + .ToList(); + + foreach (var file in eligibleFiles) + { + try + { + File.Delete(file.FullName); + } + catch (IOException ex) + { + _logger.ErrorException("Error deleting file {0}", ex, file.FullName); + } + } + } + + private void DeleteEmptyFolders(string path) + { + try + { + foreach (var d in Directory.EnumerateDirectories(path)) + { + DeleteEmptyFolders(d); + } + + var entries = Directory.EnumerateFileSystemEntries(path); + + if (!entries.Any()) + { + try + { + Directory.Delete(path); + } + catch (UnauthorizedAccessException) { } + catch (DirectoryNotFoundException) { } + } + } + catch (UnauthorizedAccessException) { } + } + } +} |
