mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-03 19:17:24 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			210 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			210 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using MediaBrowser.Controller.Configuration;
 | 
						|
using MediaBrowser.Controller.FileOrganization;
 | 
						|
using MediaBrowser.Controller.Library;
 | 
						|
using MediaBrowser.Controller.Providers;
 | 
						|
using MediaBrowser.Model.FileOrganization;
 | 
						|
using MediaBrowser.Model.Logging;
 | 
						|
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.IO;
 | 
						|
using System.Linq;
 | 
						|
using System.Threading;
 | 
						|
using System.Threading.Tasks;
 | 
						|
using MediaBrowser.Common.IO;
 | 
						|
using MediaBrowser.Controller.IO;
 | 
						|
using MediaBrowser.Model.IO;
 | 
						|
 | 
						|
namespace Emby.Server.Implementations.FileOrganization
 | 
						|
{
 | 
						|
    public class TvFolderOrganizer
 | 
						|
    {
 | 
						|
        private readonly ILibraryMonitor _libraryMonitor;
 | 
						|
        private readonly ILibraryManager _libraryManager;
 | 
						|
        private readonly ILogger _logger;
 | 
						|
        private readonly IFileSystem _fileSystem;
 | 
						|
        private readonly IFileOrganizationService _organizationService;
 | 
						|
        private readonly IServerConfigurationManager _config;
 | 
						|
        private readonly IProviderManager _providerManager;
 | 
						|
 | 
						|
        public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config, IProviderManager providerManager)
 | 
						|
        {
 | 
						|
            _libraryManager = libraryManager;
 | 
						|
            _logger = logger;
 | 
						|
            _fileSystem = fileSystem;
 | 
						|
            _libraryMonitor = libraryMonitor;
 | 
						|
            _organizationService = organizationService;
 | 
						|
            _config = config;
 | 
						|
            _providerManager = providerManager;
 | 
						|
        }
 | 
						|
 | 
						|
        private bool EnableOrganization(FileSystemMetadata fileInfo, TvFileOrganizationOptions options)
 | 
						|
        {
 | 
						|
            var minFileBytes = options.MinFileSizeMb * 1024 * 1024;
 | 
						|
 | 
						|
            try
 | 
						|
            {
 | 
						|
                return _libraryManager.IsVideoFile(fileInfo.FullName) && fileInfo.Length >= minFileBytes;
 | 
						|
            }
 | 
						|
            catch (Exception ex)
 | 
						|
            {
 | 
						|
                _logger.ErrorException("Error organizing file {0}", ex, fileInfo.Name);
 | 
						|
            }
 | 
						|
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        public async Task Organize(AutoOrganizeOptions options, CancellationToken cancellationToken, IProgress<double> progress)
 | 
						|
        {
 | 
						|
            var watchLocations = options.TvOptions.WatchLocations.ToList();
 | 
						|
 | 
						|
            var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize)
 | 
						|
                .OrderBy(_fileSystem.GetCreationTimeUtc)
 | 
						|
                .Where(i => EnableOrganization(i, options.TvOptions))
 | 
						|
                .ToList();
 | 
						|
 | 
						|
            var processedFolders = new HashSet<string>();
 | 
						|
 | 
						|
            progress.Report(10);
 | 
						|
 | 
						|
            if (eligibleFiles.Count > 0)
 | 
						|
            {
 | 
						|
                var numComplete = 0;
 | 
						|
 | 
						|
                foreach (var file in eligibleFiles)
 | 
						|
                {
 | 
						|
                    var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager,
 | 
						|
                        _libraryMonitor, _providerManager);
 | 
						|
 | 
						|
                    try
 | 
						|
                    {
 | 
						|
                        var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.TvOptions.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false);
 | 
						|
                        if (result.Status == FileSortingStatus.Success && !processedFolders.Contains(file.DirectoryName, StringComparer.OrdinalIgnoreCase))
 | 
						|
                        {
 | 
						|
                            processedFolders.Add(file.DirectoryName);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    catch (Exception ex)
 | 
						|
                    {
 | 
						|
                        _logger.ErrorException("Error organizing episode {0}", ex, file.FullName);
 | 
						|
                    }
 | 
						|
 | 
						|
                    numComplete++;
 | 
						|
                    double percent = numComplete;
 | 
						|
                    percent /= eligibleFiles.Count;
 | 
						|
 | 
						|
                    progress.Report(10 + 89 * percent);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            cancellationToken.ThrowIfCancellationRequested();
 | 
						|
            progress.Report(99);
 | 
						|
 | 
						|
            foreach (var path in processedFolders)
 | 
						|
            {
 | 
						|
                var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete
 | 
						|
                    .Select(i => i.Trim().TrimStart('.'))
 | 
						|
                    .Where(i => !string.IsNullOrEmpty(i))
 | 
						|
                    .Select(i => "." + i)
 | 
						|
                    .ToList();
 | 
						|
 | 
						|
                if (deleteExtensions.Count > 0)
 | 
						|
                {
 | 
						|
                    DeleteLeftOverFiles(path, deleteExtensions);
 | 
						|
                }
 | 
						|
 | 
						|
                if (options.TvOptions.DeleteEmptyFolders)
 | 
						|
                {
 | 
						|
                    if (!IsWatchFolder(path, watchLocations))
 | 
						|
                    {
 | 
						|
                        DeleteEmptyFolders(path);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            progress.Report(100);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Gets the files to organize.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="path">The path.</param>
 | 
						|
        /// <returns>IEnumerable{FileInfo}.</returns>
 | 
						|
        private List<FileSystemMetadata> GetFilesToOrganize(string path)
 | 
						|
        {
 | 
						|
            try
 | 
						|
            {
 | 
						|
                return _fileSystem.GetFiles(path, true)
 | 
						|
                    .ToList();
 | 
						|
            }
 | 
						|
            catch (IOException ex)
 | 
						|
            {
 | 
						|
                _logger.ErrorException("Error getting files from {0}", ex, path);
 | 
						|
 | 
						|
                return new List<FileSystemMetadata>();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Deletes the left over files.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="path">The path.</param>
 | 
						|
        /// <param name="extensions">The extensions.</param>
 | 
						|
        private void DeleteLeftOverFiles(string path, IEnumerable<string> extensions)
 | 
						|
        {
 | 
						|
            var eligibleFiles = _fileSystem.GetFiles(path, true)
 | 
						|
                .Where(i => extensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase))
 | 
						|
                .ToList();
 | 
						|
 | 
						|
            foreach (var file in eligibleFiles)
 | 
						|
            {
 | 
						|
                try
 | 
						|
                {
 | 
						|
                    _fileSystem.DeleteFile(file.FullName);
 | 
						|
                }
 | 
						|
                catch (Exception ex)
 | 
						|
                {
 | 
						|
                    _logger.ErrorException("Error deleting file {0}", ex, file.FullName);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Deletes the empty folders.
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="path">The path.</param>
 | 
						|
        private void DeleteEmptyFolders(string path)
 | 
						|
        {
 | 
						|
            try
 | 
						|
            {
 | 
						|
                foreach (var d in _fileSystem.GetDirectoryPaths(path))
 | 
						|
                {
 | 
						|
                    DeleteEmptyFolders(d);
 | 
						|
                }
 | 
						|
 | 
						|
                var entries = _fileSystem.GetFileSystemEntryPaths(path);
 | 
						|
 | 
						|
                if (!entries.Any())
 | 
						|
                {
 | 
						|
                    try
 | 
						|
                    {
 | 
						|
                        _logger.Debug("Deleting empty directory {0}", path);
 | 
						|
                        _fileSystem.DeleteDirectory(path, false);
 | 
						|
                    }
 | 
						|
                    catch (UnauthorizedAccessException) { }
 | 
						|
                    catch (IOException) { }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            catch (UnauthorizedAccessException) { }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Determines if a given folder path is contained in a folder list
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="path">The folder path to check.</param>
 | 
						|
        /// <param name="watchLocations">A list of folders.</param>
 | 
						|
        private bool IsWatchFolder(string path, IEnumerable<string> watchLocations)
 | 
						|
        {
 | 
						|
            return watchLocations.Contains(path, StringComparer.OrdinalIgnoreCase);
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |