private Task OrganizeEpisode(string sourcePath, string seriesName, int?seasonNumber, int?episodeNumber, int?endingEpiosdeNumber, DateTime?premiereDate, AutoOrganizeOptions options, bool overwriteExisting, bool rememberCorrection, FileOrganizationResult result, CancellationToken cancellationToken) { var series = GetMatchingSeries(seriesName, result, options); if (series == null) { var msg = string.Format("Unable to find series in library matching name {0}", seriesName); result.Status = FileSortingStatus.Failure; result.StatusMessage = msg; _logger.Warn(msg); return(Task.FromResult(true)); } return(OrganizeEpisode(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options, overwriteExisting, rememberCorrection, result, cancellationToken)); }
private void SaveSmartMatchString(string matchString, Series series, AutoOrganizeOptions options) { if (string.IsNullOrEmpty(matchString) || matchString.Length < 3) { return; } SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, series.Name, StringComparison.OrdinalIgnoreCase)); if (info == null) { info = new SmartMatchInfo(); info.ItemName = series.Name; info.OrganizerType = FileOrganizerType.Episode; info.DisplayName = series.Name; var list = options.SmartMatchInfos.ToList(); list.Add(info); options.SmartMatchInfos = list.ToArray(); } if (!info.MatchStrings.Contains(matchString, StringComparer.OrdinalIgnoreCase)) { var list = info.MatchStrings.ToList(); list.Add(matchString); info.MatchStrings = list.ToArray(); _config.SaveAutoOrganizeOptions(options); } }
/// <summary> /// Deletes leftover files and empty folders if configured to do so. /// </summary> /// <param name="options"></param> /// <param name="watchLocations"></param> /// <param name="folderPath"></param> public void DeleteLeftoverFilesAndEmptyFolders(AutoOrganizeOptions options, string folderPath) { var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete .Select(i => i.Trim().TrimStart('.')) .Where(i => !string.IsNullOrEmpty(i)) .Select(i => "." + i) .ToList(); if (deleteExtensions.Count > 0) { DeleteLeftOverFiles(folderPath, deleteExtensions); } if (options.TvOptions.DeleteEmptyFolders) { if (!IsWatchFolder(folderPath, options.TvOptions.WatchLocations)) { DeleteEmptyFolders(folderPath); } } }
private async Task OrganizeEpisode(string sourcePath, Series series, int?seasonNumber, int?episodeNumber, int?endingEpiosdeNumber, DateTime?premiereDate, AutoOrganizeOptions options, bool overwriteExisting, bool rememberCorrection, FileOrganizationResult result, CancellationToken cancellationToken) { _logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path); var originalExtractedSeriesString = result.ExtractedName; bool isNew = string.IsNullOrWhiteSpace(result.Id); if (isNew) { await _organizationService.SaveResult(result, cancellationToken); } if (!_organizationService.AddToInProgressList(result, isNew)) { throw new Exception("File is currently processed otherwise. Please try again later."); } try { // Proceed to sort the file var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(newPath)) { var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath); throw new Exception(msg); } _logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath); result.TargetPath = newPath; var fileExists = _fileSystem.FileExists(result.TargetPath); var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber); if (!overwriteExisting) { if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath)) { var msg = string.Format("File '{0}' already copied to new path '{1}', stopping organization", sourcePath, newPath); _logger.Info(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; return; } if (fileExists) { var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, newPath); _logger.Info(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; result.TargetPath = newPath; return; } if (otherDuplicatePaths.Count > 0) { var msg = string.Format("File '{0}' already exists as these:'{1}'. Stopping organization", sourcePath, string.Join("', '", otherDuplicatePaths)); _logger.Info(msg); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = msg; result.DuplicatePaths = otherDuplicatePaths; return; } } PerformFileSorting(options.TvOptions, result); if (overwriteExisting) { var hasRenamedFiles = false; foreach (var path in otherDuplicatePaths) { _logger.Debug("Removing duplicate episode {0}", path); _libraryMonitor.ReportFileSystemChangeBeginning(path); var renameRelatedFiles = !hasRenamedFiles && string.Equals(_fileSystem.GetDirectoryName(path), _fileSystem.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase); if (renameRelatedFiles) { hasRenamedFiles = true; } try { DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath); } catch (IOException ex) { _logger.ErrorException("Error removing duplicate episode", ex, path); } finally { _libraryMonitor.ReportFileSystemChangeComplete(path, true); } } } } catch (Exception ex) { result.Status = FileSortingStatus.Failure; result.StatusMessage = ex.Message; _logger.Warn(ex.Message); return; } finally { _organizationService.RemoveFromInprogressList(result); } if (rememberCorrection) { SaveSmartMatchString(originalExtractedSeriesString, series, options); } }
public async Task <FileOrganizationResult> OrganizeWithCorrection(EpisodeFileOrganizationRequest request, AutoOrganizeOptions options, CancellationToken cancellationToken) { var result = _organizationService.GetResult(request.ResultId); try { Series series = null; if (request.NewSeriesProviderIds.Count > 0) { // We're having a new series here SeriesInfo seriesRequest = new SeriesInfo(); seriesRequest.ProviderIds = request.NewSeriesProviderIds; var refreshOptions = new MetadataRefreshOptions(_fileSystem); series = new Series(); series.Id = Guid.NewGuid(); series.Name = request.NewSeriesName; int year; if (int.TryParse(request.NewSeriesYear, out year)) { series.ProductionYear = year; } var seriesFolderName = series.Name; if (series.ProductionYear.HasValue) { seriesFolderName = string.Format("{0} ({1})", seriesFolderName, series.ProductionYear); } seriesFolderName = _fileSystem.GetValidFilename(seriesFolderName); series.Path = Path.Combine(request.TargetFolder, seriesFolderName); series.ProviderIds = request.NewSeriesProviderIds; await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); } if (series == null) { // Existing Series series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId)); } await OrganizeEpisode(result.OriginalPath, series, request.SeasonNumber, request.EpisodeNumber, request.EndingEpisodeNumber, null, options, true, request.RememberCorrection, result, cancellationToken).ConfigureAwait(false); await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { result.Status = FileSortingStatus.Failure; result.StatusMessage = ex.Message; } return(result); }
private Series GetMatchingSeries(string seriesName, FileOrganizationResult result, AutoOrganizeOptions options) { var parsedName = _libraryManager.ParseName(seriesName); var yearInName = parsedName.Year; var nameWithoutYear = parsedName.Name; result.ExtractedName = nameWithoutYear; result.ExtractedYear = yearInName; var series = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series) .Cast <Series>() .Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i)) .Where(i => i.Item2 > 0) .OrderByDescending(i => i.Item2) .Select(i => i.Item1) .FirstOrDefault(); if (series == null) { SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(e => e.MatchStrings.Contains(nameWithoutYear, StringComparer.OrdinalIgnoreCase)); if (info != null) { series = _libraryManager.RootFolder .GetRecursiveChildren(i => i is Series) .Cast <Series>() .FirstOrDefault(i => string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase)); } } return(series); }
public static void SaveAutoOrganizeOptions(this IConfigurationManager manager, AutoOrganizeOptions options) { manager.SaveConfiguration("autoorganize", options); }
/// <summary> /// Gets the new path. /// </summary> /// <param name="sourcePath">The source path.</param> /// <param name="series">The series.</param> /// <param name="seasonNumber">The season number.</param> /// <param name="episodeNumber">The episode number.</param> /// <param name="endingEpisodeNumber">The ending episode number.</param> /// <param name="options">The options.</param> /// <returns>System.String.</returns> private string GetNewPath(string sourcePath, string movieName, string movieYear, string targetPath, AutoOrganizeOptions options, bool overwriteExisting, FileOrganizationResult result, CancellationToken cancellationToken) { var folderName = _fileSystem.GetValidFilename(movieName).Trim(); if (!string.IsNullOrEmpty(movieYear)) { folderName = string.Format("{0} ({1})", folderName, movieYear); } var newPath = Path.Combine(targetPath, folderName); var fileName = _fileSystem.GetFileNameWithoutExtension(sourcePath); fileName = string.Format("{0}{1}", fileName, Path.GetExtension(sourcePath)); newPath = Path.Combine(newPath, fileName); return(newPath); }
public abstract Task <FileOrganizationResult> OrganizeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken);
public abstract Task <FileOrganizationResult> OrganizeWithCorrection(MovieFileOrganizationRequest request, AutoOrganizeOptions options, CancellationToken cancellationToken);
private async Task OrganizeMovie(string sourcePath, string movieName, string movieYear, string targetPath, AutoOrganizeOptions options, bool overwriteExisting, FileOrganizationResult result, CancellationToken cancellationToken) { _logger.Info("Sorting file {0} into movie folder {1}", sourcePath, targetPath); // Proceed to sort the file var newPath = GetNewPath(sourcePath, movieName, movieYear, targetPath, options, true, result, cancellationToken); if (string.IsNullOrEmpty(newPath)) { var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath); result.Status = FileSortingStatus.Failure; result.StatusMessage = msg; _logger.Warn(msg); return; } _logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath); result.TargetPath = newPath; var fileExists = _fileSystem.FileExists(result.TargetPath); if (!overwriteExisting) { if (options.TvOptions.CopyOriginalFile && fileExists) { _logger.Info("File {0} already copied to new path {1}, stopping organization", sourcePath, newPath); result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = string.Empty; return; } if (fileExists) { result.Status = FileSortingStatus.SkippedExisting; result.StatusMessage = string.Empty; return; } } PerformFileSorting(options.TvOptions, result); ////if (overwriteExisting) ////{ //// var hasRenamedFiles = false; //// foreach (var path in otherDuplicatePaths) //// { //// _logger.Debug("Removing duplicate episode {0}", path); //// _libraryMonitor.ReportFileSystemChangeBeginning(path); //// var renameRelatedFiles = !hasRenamedFiles && //// string.Equals(Path.GetDirectoryName(path), Path.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase); //// if (renameRelatedFiles) //// { //// hasRenamedFiles = true; //// } //// try //// { //// DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath); //// } //// catch (IOException ex) //// { //// _logger.ErrorException("Error removing duplicate episode", ex, path); //// } //// finally //// { //// _libraryMonitor.ReportFileSystemChangeComplete(path, true); //// } //// } ////} }
public override async Task <FileOrganizationResult> OrganizeWithCorrection(MovieFileOrganizationRequest baseRequest, AutoOrganizeOptions options, CancellationToken cancellationToken) { var request = (MovieFileOrganizationRequest)baseRequest; var result = _organizationService.GetResult(request.ResultId); var file = _fileSystem.GetFileInfo(result.OriginalPath); result.Type = FileOrganizerType.Movie; await OrganizeMovie(result.OriginalPath, request.Name, request.Year, request.TargetFolder, options, true, result, cancellationToken).ConfigureAwait(false); await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); if (file != null && file.Exists && file.DirectoryName != null) { this.DeleteLeftoverFilesAndEmptyFolders(options, file.DirectoryName); } return(result); }
public override Task <FileOrganizationResult> OrganizeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken) { throw new NotImplementedException("Auto-Organize is not implemented for movie files at this time."); }
private Series GetMatchingSeries(string seriesName, FileOrganizationResult result, AutoOrganizeOptions options) { var parsedName = _libraryManager.ParseName(seriesName); var yearInName = parsedName.Year; var nameWithoutYear = parsedName.Name; result.ExtractedName = nameWithoutYear; result.ExtractedYear = yearInName; var series = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Series).Name }, Recursive = true, DtoOptions = new DtoOptions(true) }) .Cast <Series>() .Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i)) .Where(i => i.Item2 > 0) .OrderByDescending(i => i.Item2) .Select(i => i.Item1) .FirstOrDefault(); if (series == null) { SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(e => e.MatchStrings.Contains(nameWithoutYear, StringComparer.OrdinalIgnoreCase)); if (info != null) { series = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Series).Name }, Recursive = true, Name = info.ItemName, DtoOptions = new DtoOptions(true) }).Cast <Series>().FirstOrDefault(); } } return(series); }
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); } 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); }
public async Task <FileOrganizationResult> OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken) { _logger.Info("Sorting file {0}", path); var result = new FileOrganizationResult { Date = DateTime.UtcNow, OriginalPath = path, OriginalFileName = Path.GetFileName(path), Type = FileOrganizerType.Episode, FileSize = _fileSystem.GetFileInfo(path).Length }; try { if (_libraryMonitor.IsPathLocked(path)) { result.Status = FileSortingStatus.Failure; result.StatusMessage = "Path is locked by other processes. Please try again later."; return(result); } var namingOptions = GetNamingOptionsInternal(); var resolver = new EpisodeResolver(namingOptions); var episodeInfo = resolver.Resolve(path, false) ?? new Emby.Naming.TV.EpisodeInfo(); var seriesName = episodeInfo.SeriesName; if (!string.IsNullOrEmpty(seriesName)) { var seasonNumber = episodeInfo.SeasonNumber; result.ExtractedSeasonNumber = seasonNumber; // Passing in true will include a few extra regex's var episodeNumber = episodeInfo.EpisodeNumber; result.ExtractedEpisodeNumber = episodeNumber; var premiereDate = episodeInfo.IsByDate ? new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) : (DateTime?)null; if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue)) { if (episodeInfo.IsByDate) { _logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value); } else { _logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber); } var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber; result.ExtractedEndingEpisodeNumber = endingEpisodeNumber; await OrganizeEpisode(path, seriesName, seasonNumber, episodeNumber, endingEpisodeNumber, premiereDate, options, overwriteExisting, false, result, cancellationToken).ConfigureAwait(false); } else { var msg = string.Format("Unable to determine episode number from {0}", path); result.Status = FileSortingStatus.Failure; result.StatusMessage = msg; _logger.Warn(msg); } } else { var msg = string.Format("Unable to determine series name from {0}", path); result.Status = FileSortingStatus.Failure; result.StatusMessage = msg; _logger.Warn(msg); } var previousResult = _organizationService.GetResultBySourcePath(path); if (previousResult != null) { // Don't keep saving the same result over and over if nothing has changed if (previousResult.Status == result.Status && previousResult.StatusMessage == result.StatusMessage && result.Status != FileSortingStatus.Success) { return(previousResult); } } await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { result.Status = FileSortingStatus.Failure; result.StatusMessage = ex.Message; } return(result); }
public async Task <FileOrganizationResult> OrganizeWithCorrection(EpisodeFileOrganizationRequest request, AutoOrganizeOptions options, CancellationToken cancellationToken) { var result = _organizationService.GetResult(request.ResultId); var series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId)); await OrganizeEpisode(result.OriginalPath, series, request.SeasonNumber, request.EpisodeNumber, request.EndingEpisodeNumber, null, options, true, request.RememberCorrection, result, cancellationToken).ConfigureAwait(false); await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); return(result); }