2016-03-27 21:11:27 +00:00
|
|
|
|
using MediaBrowser.Controller.Configuration;
|
2014-01-22 17:05:06 +00:00
|
|
|
|
using MediaBrowser.Controller.FileOrganization;
|
|
|
|
|
using MediaBrowser.Controller.Library;
|
2014-02-20 04:53:15 +00:00
|
|
|
|
using MediaBrowser.Controller.Providers;
|
2014-01-22 17:05:06 +00:00
|
|
|
|
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;
|
2016-10-25 19:02:04 +00:00
|
|
|
|
using MediaBrowser.Common.IO;
|
|
|
|
|
using MediaBrowser.Controller.IO;
|
|
|
|
|
using MediaBrowser.Model.IO;
|
2014-01-22 17:05:06 +00:00
|
|
|
|
|
2016-11-03 06:37:52 +00:00
|
|
|
|
namespace Emby.Server.Implementations.FileOrganization
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
|
|
|
|
public class TvFolderOrganizer
|
|
|
|
|
{
|
2014-01-28 21:25:10 +00:00
|
|
|
|
private readonly ILibraryMonitor _libraryMonitor;
|
2014-01-22 17:05:06 +00:00
|
|
|
|
private readonly ILibraryManager _libraryManager;
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
|
private readonly IFileSystem _fileSystem;
|
|
|
|
|
private readonly IFileOrganizationService _organizationService;
|
|
|
|
|
private readonly IServerConfigurationManager _config;
|
2014-02-20 04:53:15 +00:00
|
|
|
|
private readonly IProviderManager _providerManager;
|
2014-01-22 17:05:06 +00:00
|
|
|
|
|
2014-02-20 04:53:15 +00:00
|
|
|
|
public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config, IProviderManager providerManager)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
|
|
|
|
_libraryManager = libraryManager;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_fileSystem = fileSystem;
|
2014-01-28 21:25:10 +00:00
|
|
|
|
_libraryMonitor = libraryMonitor;
|
2014-01-22 17:05:06 +00:00
|
|
|
|
_organizationService = organizationService;
|
|
|
|
|
_config = config;
|
2014-02-20 04:53:15 +00:00
|
|
|
|
_providerManager = providerManager;
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-04 03:38:46 +00:00
|
|
|
|
private bool EnableOrganization(FileSystemMetadata fileInfo, TvFileOrganizationOptions options)
|
2015-10-02 23:08:01 +00:00
|
|
|
|
{
|
2016-02-05 02:01:03 +00:00
|
|
|
|
var minFileBytes = options.MinFileSizeMb * 1024 * 1024;
|
2015-10-04 03:38:46 +00:00
|
|
|
|
|
2015-10-02 23:08:01 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2015-10-04 03:38:46 +00:00
|
|
|
|
return _libraryManager.IsVideoFile(fileInfo.FullName) && fileInfo.Length >= minFileBytes;
|
2015-10-02 23:08:01 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.ErrorException("Error organizing file {0}", ex, fileInfo.Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-29 19:23:22 +00:00
|
|
|
|
private bool IsValidWatchLocation(string path, List<string> libraryFolderPaths)
|
|
|
|
|
{
|
|
|
|
|
if (IsPathAlreadyInMediaLibrary(path, libraryFolderPaths))
|
|
|
|
|
{
|
|
|
|
|
_logger.Info("Folder {0} is not eligible for auto-organize because it is also part of an Emby library", path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsPathAlreadyInMediaLibrary(string path, List<string> libraryFolderPaths)
|
|
|
|
|
{
|
|
|
|
|
return libraryFolderPaths.Any(i => string.Equals(i, path, StringComparison.Ordinal) || _fileSystem.ContainsSubPath(i, path));
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-23 04:12:46 +00:00
|
|
|
|
public async Task Organize(AutoOrganizeOptions options, CancellationToken cancellationToken, IProgress<double> progress)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
2017-03-29 19:23:22 +00:00
|
|
|
|
var libraryFolderPaths = _libraryManager.GetVirtualFolders().SelectMany(i => i.Locations).ToList();
|
|
|
|
|
|
|
|
|
|
var watchLocations = options.TvOptions.WatchLocations
|
|
|
|
|
.Where(i => IsValidWatchLocation(i, libraryFolderPaths))
|
|
|
|
|
.ToList();
|
2014-01-22 17:05:06 +00:00
|
|
|
|
|
|
|
|
|
var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize)
|
|
|
|
|
.OrderBy(_fileSystem.GetCreationTimeUtc)
|
2016-02-05 02:01:03 +00:00
|
|
|
|
.Where(i => EnableOrganization(i, options.TvOptions))
|
2014-01-22 17:05:06 +00:00
|
|
|
|
.ToList();
|
|
|
|
|
|
2015-09-19 01:22:23 +00:00
|
|
|
|
var processedFolders = new HashSet<string>();
|
|
|
|
|
|
2014-01-22 17:05:06 +00:00
|
|
|
|
progress.Report(10);
|
|
|
|
|
|
|
|
|
|
if (eligibleFiles.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var numComplete = 0;
|
|
|
|
|
|
|
|
|
|
foreach (var file in eligibleFiles)
|
|
|
|
|
{
|
2017-03-29 19:16:43 +00:00
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
2014-01-22 17:05:06 +00:00
|
|
|
|
var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager,
|
2014-02-20 04:53:15 +00:00
|
|
|
|
_libraryMonitor, _providerManager);
|
2014-01-22 17:05:06 +00:00
|
|
|
|
|
2015-06-08 21:32:20 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2015-09-23 04:12:46 +00:00
|
|
|
|
var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.TvOptions.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false);
|
2017-03-29 19:16:43 +00:00
|
|
|
|
|
2015-09-29 03:35:50 +00:00
|
|
|
|
if (result.Status == FileSortingStatus.Success && !processedFolders.Contains(file.DirectoryName, StringComparer.OrdinalIgnoreCase))
|
2015-09-19 01:22:23 +00:00
|
|
|
|
{
|
|
|
|
|
processedFolders.Add(file.DirectoryName);
|
|
|
|
|
}
|
2015-06-08 21:32:20 +00:00
|
|
|
|
}
|
2017-03-29 19:16:43 +00:00
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-06-08 21:32:20 +00:00
|
|
|
|
catch (Exception ex)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
2016-03-04 17:07:14 +00:00
|
|
|
|
_logger.ErrorException("Error organizing episode {0}", ex, file.FullName);
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
numComplete++;
|
|
|
|
|
double percent = numComplete;
|
|
|
|
|
percent /= eligibleFiles.Count;
|
|
|
|
|
|
2016-03-27 21:11:27 +00:00
|
|
|
|
progress.Report(10 + 89 * percent);
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
progress.Report(99);
|
|
|
|
|
|
2015-09-19 01:22:23 +00:00
|
|
|
|
foreach (var path in processedFolders)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
2015-09-23 04:12:46 +00:00
|
|
|
|
var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete
|
2014-01-29 20:30:26 +00:00
|
|
|
|
.Select(i => i.Trim().TrimStart('.'))
|
|
|
|
|
.Where(i => !string.IsNullOrEmpty(i))
|
|
|
|
|
.Select(i => "." + i)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
if (deleteExtensions.Count > 0)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
2014-01-29 20:30:26 +00:00
|
|
|
|
DeleteLeftOverFiles(path, deleteExtensions);
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-23 04:12:46 +00:00
|
|
|
|
if (options.TvOptions.DeleteEmptyFolders)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
2015-09-19 01:22:23 +00:00
|
|
|
|
if (!IsWatchFolder(path, watchLocations))
|
2014-01-22 17:22:04 +00:00
|
|
|
|
{
|
2015-09-19 01:22:23 +00:00
|
|
|
|
DeleteEmptyFolders(path);
|
2014-01-22 17:22:04 +00:00
|
|
|
|
}
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
progress.Report(100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the files to organize.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="path">The path.</param>
|
|
|
|
|
/// <returns>IEnumerable{FileInfo}.</returns>
|
2015-10-19 10:51:20 +00:00
|
|
|
|
private List<FileSystemMetadata> GetFilesToOrganize(string path)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2016-03-04 17:07:14 +00:00
|
|
|
|
return _fileSystem.GetFiles(path, true)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
catch (IOException ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.ErrorException("Error getting files from {0}", ex, path);
|
|
|
|
|
|
2015-10-04 03:38:46 +00:00
|
|
|
|
return new List<FileSystemMetadata>();
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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)
|
|
|
|
|
{
|
2017-03-30 17:56:32 +00:00
|
|
|
|
var eligibleFiles = _fileSystem.GetFilePaths(path, extensions.ToArray(), false, true)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
foreach (var file in eligibleFiles)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2017-03-30 17:56:32 +00:00
|
|
|
|
_fileSystem.DeleteFile(file);
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
2014-02-26 04:38:21 +00:00
|
|
|
|
catch (Exception ex)
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
2017-03-30 17:56:32 +00:00
|
|
|
|
_logger.ErrorException("Error deleting file {0}", ex, file);
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Deletes the empty folders.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="path">The path.</param>
|
|
|
|
|
private void DeleteEmptyFolders(string path)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2015-09-24 17:50:49 +00:00
|
|
|
|
foreach (var d in _fileSystem.GetDirectoryPaths(path))
|
2014-01-22 17:05:06 +00:00
|
|
|
|
{
|
|
|
|
|
DeleteEmptyFolders(d);
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-24 17:50:49 +00:00
|
|
|
|
var entries = _fileSystem.GetFileSystemEntryPaths(path);
|
2014-01-22 17:05:06 +00:00
|
|
|
|
|
|
|
|
|
if (!entries.Any())
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2014-01-22 17:22:04 +00:00
|
|
|
|
_logger.Debug("Deleting empty directory {0}", path);
|
2015-09-13 23:07:54 +00:00
|
|
|
|
_fileSystem.DeleteDirectory(path, false);
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
catch (UnauthorizedAccessException) { }
|
2016-11-03 06:37:52 +00:00
|
|
|
|
catch (IOException) { }
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (UnauthorizedAccessException) { }
|
|
|
|
|
}
|
2015-09-19 01:22:23 +00:00
|
|
|
|
|
|
|
|
|
/// <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)
|
|
|
|
|
{
|
2015-09-29 03:35:50 +00:00
|
|
|
|
return watchLocations.Contains(path, StringComparer.OrdinalIgnoreCase);
|
2015-09-19 01:22:23 +00:00
|
|
|
|
}
|
2014-01-22 17:05:06 +00:00
|
|
|
|
}
|
2016-03-04 17:07:14 +00:00
|
|
|
|
}
|