|
|
|
@ -61,92 +61,92 @@ namespace Emby.Server.Implementations.FileOrganization
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_libraryMonitor.IsPathLocked(path))
|
|
|
|
|
{
|
|
|
|
|
result.Status = FileSortingStatus.Failure;
|
|
|
|
|
result.StatusMessage = "Path is locked by other processes. Please try again later.";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
if (_libraryMonitor.IsPathLocked(path))
|
|
|
|
|
{
|
|
|
|
|
result.Status = FileSortingStatus.Failure;
|
|
|
|
|
result.StatusMessage = "Path is locked by other processes. Please try again later.";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
|
|
|
|
var resolver = new EpisodeResolver(namingOptions, new NullLogger());
|
|
|
|
|
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
|
|
|
|
var resolver = new EpisodeResolver(namingOptions, new NullLogger());
|
|
|
|
|
|
|
|
|
|
var episodeInfo = resolver.Resolve(path, false) ??
|
|
|
|
|
new MediaBrowser.Naming.TV.EpisodeInfo();
|
|
|
|
|
var episodeInfo = resolver.Resolve(path, false) ??
|
|
|
|
|
new MediaBrowser.Naming.TV.EpisodeInfo();
|
|
|
|
|
|
|
|
|
|
var seriesName = episodeInfo.SeriesName;
|
|
|
|
|
var seriesName = episodeInfo.SeriesName;
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(seriesName))
|
|
|
|
|
{
|
|
|
|
|
var seasonNumber = episodeInfo.SeasonNumber;
|
|
|
|
|
if (!string.IsNullOrEmpty(seriesName))
|
|
|
|
|
{
|
|
|
|
|
var seasonNumber = episodeInfo.SeasonNumber;
|
|
|
|
|
|
|
|
|
|
result.ExtractedSeasonNumber = seasonNumber;
|
|
|
|
|
result.ExtractedSeasonNumber = seasonNumber;
|
|
|
|
|
|
|
|
|
|
// Passing in true will include a few extra regex's
|
|
|
|
|
var episodeNumber = episodeInfo.EpisodeNumber;
|
|
|
|
|
// Passing in true will include a few extra regex's
|
|
|
|
|
var episodeNumber = episodeInfo.EpisodeNumber;
|
|
|
|
|
|
|
|
|
|
result.ExtractedEpisodeNumber = episodeNumber;
|
|
|
|
|
result.ExtractedEpisodeNumber = episodeNumber;
|
|
|
|
|
|
|
|
|
|
var premiereDate = episodeInfo.IsByDate ?
|
|
|
|
|
new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) :
|
|
|
|
|
(DateTime?)null;
|
|
|
|
|
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)
|
|
|
|
|
if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue))
|
|
|
|
|
{
|
|
|
|
|
_logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value);
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
_logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber);
|
|
|
|
|
var msg = string.Format("Unable to determine episode number from {0}", path);
|
|
|
|
|
result.Status = FileSortingStatus.Failure;
|
|
|
|
|
result.StatusMessage = msg;
|
|
|
|
|
_logger.Warn(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
var msg = string.Format("Unable to determine series name 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);
|
|
|
|
|
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)
|
|
|
|
|
if (previousResult != null)
|
|
|
|
|
{
|
|
|
|
|
return previousResult;
|
|
|
|
|
// 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);
|
|
|
|
|
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
@ -162,58 +162,60 @@ namespace Emby.Server.Implementations.FileOrganization
|
|
|
|
|
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;
|
|
|
|
|
Series series = null;
|
|
|
|
|
|
|
|
|
|
int year;
|
|
|
|
|
if (int.TryParse(request.NewSeriesYear, out year))
|
|
|
|
|
if (request.NewSeriesProviderIds.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
series.ProductionYear = year;
|
|
|
|
|
}
|
|
|
|
|
// We're having a new series here
|
|
|
|
|
SeriesInfo seriesRequest = new SeriesInfo();
|
|
|
|
|
seriesRequest.ProviderIds = request.NewSeriesProviderIds;
|
|
|
|
|
|
|
|
|
|
var seriesFolderName = series.Name;
|
|
|
|
|
if (series.ProductionYear.HasValue)
|
|
|
|
|
{
|
|
|
|
|
seriesFolderName = string.Format("{0} ({1})", seriesFolderName, series.ProductionYear);
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
series.Path = Path.Combine(request.TargetFolder, seriesFolderName);
|
|
|
|
|
var seriesFolderName = series.Name;
|
|
|
|
|
if (series.ProductionYear.HasValue)
|
|
|
|
|
{
|
|
|
|
|
seriesFolderName = string.Format("{0} ({1})", seriesFolderName, series.ProductionYear);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
series.ProviderIds = request.NewSeriesProviderIds;
|
|
|
|
|
seriesFolderName = _fileSystem.GetValidFilename(seriesFolderName);
|
|
|
|
|
|
|
|
|
|
await series.RefreshMetadata(refreshOptions, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
series.Path = Path.Combine(request.TargetFolder, seriesFolderName);
|
|
|
|
|
|
|
|
|
|
if (series == null)
|
|
|
|
|
{
|
|
|
|
|
// Existing Series
|
|
|
|
|
series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId));
|
|
|
|
|
}
|
|
|
|
|
series.ProviderIds = request.NewSeriesProviderIds;
|
|
|
|
|
|
|
|
|
|
await OrganizeEpisode(result.OriginalPath,
|
|
|
|
|
series,
|
|
|
|
|
request.SeasonNumber,
|
|
|
|
|
request.EpisodeNumber,
|
|
|
|
|
request.EndingEpisodeNumber,
|
|
|
|
|
null,
|
|
|
|
|
options,
|
|
|
|
|
true,
|
|
|
|
|
request.RememberCorrection,
|
|
|
|
|
result,
|
|
|
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
|
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);
|
|
|
|
|
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
@ -287,91 +289,91 @@ namespace Emby.Server.Implementations.FileOrganization
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
// Proceed to sort the file
|
|
|
|
|
var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
if (fileExists)
|
|
|
|
|
if (string.IsNullOrEmpty(newPath))
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath);
|
|
|
|
|
throw new Exception(msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath);
|
|
|
|
|
result.TargetPath = newPath;
|
|
|
|
|
|
|
|
|
|
PerformFileSorting(options.TvOptions, result);
|
|
|
|
|
|
|
|
|
|
if (overwriteExisting)
|
|
|
|
|
{
|
|
|
|
|
var hasRenamedFiles = false;
|
|
|
|
|
var fileExists = _fileSystem.FileExists(result.TargetPath);
|
|
|
|
|
var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber);
|
|
|
|
|
|
|
|
|
|
foreach (var path in otherDuplicatePaths)
|
|
|
|
|
if (!overwriteExisting)
|
|
|
|
|
{
|
|
|
|
|
_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)
|
|
|
|
|
if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath))
|
|
|
|
|
{
|
|
|
|
|
hasRenamedFiles = true;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
if (fileExists)
|
|
|
|
|
{
|
|
|
|
|
DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
catch (IOException ex)
|
|
|
|
|
|
|
|
|
|
if (otherDuplicatePaths.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
_logger.ErrorException("Error removing duplicate episode", ex, path);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PerformFileSorting(options.TvOptions, result);
|
|
|
|
|
|
|
|
|
|
if (overwriteExisting)
|
|
|
|
|
{
|
|
|
|
|
var hasRenamedFiles = false;
|
|
|
|
|
|
|
|
|
|
foreach (var path in otherDuplicatePaths)
|
|
|
|
|
{
|
|
|
|
|
_libraryMonitor.ReportFileSystemChangeComplete(path, true);
|
|
|
|
|
_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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
result.Status = FileSortingStatus.Failure;
|
|
|
|
|