diff --git a/Emby.AutoOrganize/Configuration/autoorganizelog.js b/Emby.AutoOrganize/Configuration/autoorganizelog.js
index 214c72e..aa0025e 100644
--- a/Emby.AutoOrganize/Configuration/autoorganizelog.js
+++ b/Emby.AutoOrganize/Configuration/autoorganizelog.js
@@ -193,11 +193,16 @@
return;
}
- var message = 'The following file will be moved from:' + ' ' + item.OriginalPath + ' ' + 'To:' + ' ' + item.TargetPath;
+ for (var i = 0; i < item.DuplicatePaths.length; i++) { //remove the target filename from the list (as we will overwrite it and better for display)
+ if (item.DuplicatePaths[i].replace(/^.*[\\\/]/, '') === item.TargetPath.replace(/^.*[\\\/]/, '')) {
+ item.DuplicatePaths.splice(i, 1);
+ i--;
+ }
+ }
- if (item.DuplicatePaths.length) {
+ var message = 'The following file will be moved from:' + ' ' + item.OriginalPath + ' ' + 'To:' + ' ' + item.TargetPath;
+ if (item.DuplicatePaths.length > 0) {
message += ' ' + 'The following duplicates will be deleted:';
-
message += ' ' + item.DuplicatePaths.join(' ');
}
@@ -386,9 +391,9 @@
var html = '';
- html += '
';
+ html += ' ';
var hide = item.IsInProgress ? '' : ' hide';
- html += ' ';
+ html += ' ';
html += ' ';
html += '';
diff --git a/Emby.AutoOrganize/Configuration/autoorganizetv.html b/Emby.AutoOrganize/Configuration/autoorganizetv.html
index 094a2e7..52a5711 100644
--- a/Emby.AutoOrganize/Configuration/autoorganizetv.html
+++ b/Emby.AutoOrganize/Configuration/autoorganizetv.html
@@ -180,8 +180,14 @@
-
-
+
+
+
+
+ Allow episodes titled 'TBA'
+
+
'TBA' is often set before the true name of an episode is known. Disabling this will prevent episodes being moved until a valid name is provided.
+
Copy
@@ -189,11 +195,20 @@
Copy or move files from the watch folder
-
-
+
+
Overwrite existing episodes
+
This option allows you to overwrite files matching the destination name
+
+
+
+
+ Only keep single format of episode
+
+
Enabled - Sorting will enforce a single copy of an episode, removing any copies regardless of the file pattern set.
+
Disabled - Sorting will allow multiple copies of an episode, as long as the file pattern does not match.
diff --git a/Emby.AutoOrganize/Configuration/autoorganizetv.js b/Emby.AutoOrganize/Configuration/autoorganizetv.js
index 02bbd48..0401e6a 100644
--- a/Emby.AutoOrganize/Configuration/autoorganizetv.js
+++ b/Emby.AutoOrganize/Configuration/autoorganizetv.js
@@ -141,7 +141,9 @@
var tvOptions = config.TvOptions;
view.querySelector('#chkEnableTvSorting').checked = tvOptions.IsEnabled;
+ view.querySelector('#chkAllowTBA').checked = tvOptions.AllowTBA;
view.querySelector('#chkOverwriteExistingEpisodes').checked = tvOptions.OverwriteExistingEpisodes;
+ view.querySelector('#chkSingleEpisodeVersion').checked = tvOptions.SingleEpisodeVersion;
view.querySelector('#chkDeleteEmptyFolders').checked = tvOptions.DeleteEmptyFolders;
view.querySelector('#txtMinFileSize').value = tvOptions.MinFileSizeMb;
@@ -170,7 +172,9 @@
var tvOptions = config.TvOptions;
tvOptions.IsEnabled = view.querySelector('#chkEnableTvSorting').checked;
+ tvOptions.AllowTBA = view.querySelector('#chkAllowTBA').checked;
tvOptions.OverwriteExistingEpisodes = view.querySelector('#chkOverwriteExistingEpisodes').checked;
+ tvOptions.SingleEpisodeVersion = view.querySelector('#chkSingleEpisodeVersion').checked;
tvOptions.DeleteEmptyFolders = view.querySelector('#chkDeleteEmptyFolders').checked;
tvOptions.MinFileSizeMb = view.querySelector('#txtMinFileSize').value;
diff --git a/Emby.AutoOrganize/Core/EpisodeFileOrganizer.cs b/Emby.AutoOrganize/Core/EpisodeFileOrganizer.cs
index 8ce5d5c..ae2a081 100644
--- a/Emby.AutoOrganize/Core/EpisodeFileOrganizer.cs
+++ b/Emby.AutoOrganize/Core/EpisodeFileOrganizer.cs
@@ -188,12 +188,13 @@ await OrganizeEpisode(path,
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = ex.Message;
+ _logger.ErrorException("Error organizing file {0}", ex, path);
}
catch (Exception ex)
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = ex.Message;
- _logger.ErrorException("Error organizing file", ex);
+ _logger.ErrorException("Error organizing file {0}", ex, path);
}
_organizationService.SaveResult(result, CancellationToken.None);
@@ -337,6 +338,7 @@ await OrganizeEpisode(result.OriginalPath,
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = ex.Message;
+ _logger.ErrorException("Error organizing file {0}", ex, result.OriginalPath);
}
return result;
@@ -451,7 +453,11 @@ private void OrganizeEpisode(string sourcePath,
if (!_organizationService.AddToInProgressList(result, isNew))
{
- throw new OrganizationException("File is currently processed otherwise. Please try again later.");
+ var msg = string.Format("File {0} is currently processed otherwise. Please try again later.", sourcePath);
+ _logger.Warn(msg + " Stopping organization");
+ result.Status = FileSortingStatus.Failure;
+ result.StatusMessage = msg;
+ return;
}
try
@@ -462,78 +468,87 @@ private void OrganizeEpisode(string sourcePath,
if (string.IsNullOrEmpty(newPath))
{
var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath);
- throw new OrganizationException(msg);
+ _logger.Info(msg + " Stopping organization");
+ result.Status = FileSortingStatus.Failure;
+ result.StatusMessage = msg;
+ return;
}
_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, episode);
-
+ if (options.SingleEpisodeVersion) //add value here to ensure returned to user regardless of result below
+ {
+ result.DuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, episode);
+ _logger.Info(string.Format("otherDuplicatePaths: '{0}'", string.Join("', '", result.DuplicatePaths)));
+ }
+
if (!options.OverwriteExistingEpisodes)
{
- if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath))
+ if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath) && result.DuplicatePaths.Count == 1)
{
- var msg = string.Format("File '{0}' already copied to new path '{1}', stopping organization", sourcePath, newPath);
- _logger.Info(msg);
+ var msg = string.Format("File '{0}' already copied to new path '{1}.'", sourcePath, newPath);
+ _logger.Info(msg + " Stopping organization");
result.Status = FileSortingStatus.SkippedExisting;
result.StatusMessage = msg;
return;
}
- if (fileExists)
+ if (result.DuplicatePaths.Count > 0)
{
- var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, newPath);
- _logger.Info(msg);
+ var msg = string.Format("File '{0}' already exists as: '{1}'.", sourcePath, string.Join("', '", result.DuplicatePaths), (result.DuplicatePaths.Count > 1 ? "these" : ""));
+ _logger.Info(msg + " Stopping organization");
result.Status = FileSortingStatus.SkippedExisting;
result.StatusMessage = msg;
- result.TargetPath = newPath;
return;
}
- if (otherDuplicatePaths.Count > 0)
+ if (fileExists)
{
- var msg = string.Format("File '{0}' already exists as these:'{1}'. Stopping organization", sourcePath, string.Join("', '", otherDuplicatePaths));
- _logger.Info(msg);
+ var msg = string.Format("File '{0}' already exists as '{1}'.", sourcePath, newPath);
+ _logger.Info(msg + " Stopping organization");
result.Status = FileSortingStatus.SkippedExisting;
result.StatusMessage = msg;
- result.DuplicatePaths = otherDuplicatePaths;
+ result.TargetPath = newPath;
return;
}
}
PerformFileSorting(options, result);
- if (options.OverwriteExistingEpisodes)
+ if (options.SingleEpisodeVersion)
{
var hasRenamedFiles = false;
- foreach (var path in otherDuplicatePaths)
+ foreach (var path in result.DuplicatePaths)
{
- _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
+ if (!string.Equals(path, newPath, StringComparison.OrdinalIgnoreCase))//dont remove file matching destination path
{
- DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath);
- }
- catch (IOException ex)
- {
- _logger.ErrorException("Error removing duplicate episode", ex, path);
- }
- finally
- {
- _libraryMonitor.ReportFileSystemChangeComplete(path, true);
+ _logger.Info("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 {0}", ex, path);
+ }
+ finally
+ {
+ _libraryMonitor.ReportFileSystemChangeComplete(path, true);
+ }
}
}
}
@@ -541,8 +556,8 @@ private void OrganizeEpisode(string sourcePath,
catch (Exception ex)
{
result.Status = FileSortingStatus.Failure;
- result.StatusMessage = ex.Message;
- _logger.Warn(ex.Message);
+ result.StatusMessage = string.Format("Error sorting episode: '{0}'.", ex.Message);
+ _logger.ErrorException("Error sorting episode: {0}", ex, episode.Path);
return;
}
finally
@@ -624,7 +639,7 @@ private List
GetOtherDuplicatePaths(string targetPath,
Episode episode)
{
// TODO: Support date-naming?
- if (!series.ParentIndexNumber.HasValue || !episode.IndexNumber.HasValue)
+ if (!episode.ParentIndexNumber.HasValue || !episode.IndexNumber.HasValue)
{
return new List();
}
@@ -643,9 +658,9 @@ private List GetOtherDuplicatePaths(string targetPath,
// Must be file system based and match exactly
if (locationType != LocationType.Virtual &&
i.ParentIndexNumber.HasValue &&
- i.ParentIndexNumber.Value == series.ParentIndexNumber &&
+ i.ParentIndexNumber.Value == episode.ParentIndexNumber.Value &&
i.IndexNumber.HasValue &&
- i.IndexNumber.Value == episode.IndexNumber)
+ i.IndexNumber.Value == episode.IndexNumber.Value)
{
if (episode.IndexNumberEnd.HasValue || i.IndexNumberEnd.HasValue)
@@ -653,10 +668,9 @@ private List GetOtherDuplicatePaths(string targetPath,
return episode.IndexNumberEnd.HasValue && i.IndexNumberEnd.HasValue &&
episode.IndexNumberEnd.Value == i.IndexNumberEnd.Value;
}
-
+
return true;
}
-
return false;
})
.Select(i => i.Path)
@@ -677,7 +691,9 @@ private List GetOtherDuplicatePaths(string targetPath,
// No big deal. Maybe the season folder doesn't already exist.
}
- return episodePaths.Where(i => !string.Equals(i, targetPath, StringComparison.OrdinalIgnoreCase))
+ //include all results (including direct matches to the destination path so they can be reported in UI
+ //removed on a case by case where called elsewhere
+ return episodePaths
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
@@ -733,7 +749,7 @@ private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizat
}
catch (Exception ex)
{
- _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath);
+ _logger.ErrorException("Error deleting file {0}", ex, result.OriginalPath);
}
}
}
@@ -791,7 +807,7 @@ private Season GetMatchingSeason(Series series, Episode episode, TvFileOrganizat
if (!episode.ParentIndexNumber.HasValue)
{
var msg = string.Format("No season found for {0} season {1} episode {2}", series.Name,
- episode.ParentIndexNumber, episode.IndexNumber);
+ episode.ParentIndexNumber.Value, episode.IndexNumber.Value);
_logger.Warn(msg);
throw new OrganizationException(msg);
}
@@ -866,7 +882,22 @@ private string GetSeriesDirectoryName(string seriesName, int? seriesYear, TvFile
if (seriesYear.HasValue)
{
- seriesFullName = string.Format("{0} ({1})", seriesFullName, seriesYear);
+ var parsedName = _libraryManager.ParseName(seriesName.AsSpan());
+
+ var yearInName = parsedName.Year;
+ var nameWithoutYear = parsedName.Name;
+
+ if (string.IsNullOrWhiteSpace(nameWithoutYear))
+ {
+ nameWithoutYear = seriesName;
+ }
+
+ if (!yearInName.HasValue)
+ {
+ yearInName = seriesYear;
+ }
+
+ seriesFullName = string.Format("{0} ({1})", nameWithoutYear, seriesYear);
}
var seasonFolderName = options.SeriesFolderPattern.
@@ -928,7 +959,7 @@ private async Task CreateNewEpisode(
if (episodeSearch == null)
{
- var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
+ var msg = string.Format("No provider metadata found for {0} Season {1} Episode {2}", series.Name, seasonNumber, episodeNumber);
_logger.Warn(msg);
throw new OrganizationException(msg);
}
@@ -1003,6 +1034,10 @@ private void SetEpisodeFileName(string sourcePath, string seriesName, Season sea
var episodeTitle = _fileSystem.GetValidFilename(episode.Name).Trim();
+ if(options.AllowTBA == false && episodeTitle == "TBA")
+ {
+ throw new OrganizationException("Returned metadata title is 'TBA'. Current settings prevent this file from being moved.");
+ }
if (!episode.IndexNumber.HasValue || !season.IndexNumber.HasValue)
{
throw new OrganizationException("GetEpisodeFileName: Mandatory param as missing!");
diff --git a/Emby.AutoOrganize/Core/FileOrganizationService.cs b/Emby.AutoOrganize/Core/FileOrganizationService.cs
index 67ba8d4..1df5884 100644
--- a/Emby.AutoOrganize/Core/FileOrganizationService.cs
+++ b/Emby.AutoOrganize/Core/FileOrganizationService.cs
@@ -128,7 +128,7 @@ public void DeleteOriginalFile(string resultId)
}
catch (Exception ex)
{
- _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath);
+ _logger.ErrorException("Error deleting file {0}", ex, result.OriginalPath);
}
finally
{
@@ -177,11 +177,6 @@ public async Task PerformOrganization(string resultId)
default:
throw new OrganizationException("No organizer exist for the type " + result.Type);
}
-
- if (organizeResult.Status != FileSortingStatus.Success)
- {
- throw new OrganizationException(result.StatusMessage);
- }
}
public void ClearLog()
@@ -203,11 +198,6 @@ public async Task PerformOrganization(EpisodeFileOrganizationRequest request)
var options = GetAutoOrganizeOptions();
var result = await organizer.OrganizeWithCorrection(request, options.TvOptions, CancellationToken.None).ConfigureAwait(false);
-
- if (result.Status != FileSortingStatus.Success)
- {
- throw new Exception(result.StatusMessage);
- }
}
public void PerformOrganization(MovieFileOrganizationRequest request)
@@ -217,11 +207,6 @@ public void PerformOrganization(MovieFileOrganizationRequest request)
var options = GetAutoOrganizeOptions();
var result = organizer.OrganizeWithCorrection(request, options.MovieOptions, CancellationToken.None);
-
- if (result.Status != FileSortingStatus.Success)
- {
- throw new Exception(result.StatusMessage);
- }
}
public QueryResult GetSmartMatchInfos(FileOrganizationResultQuery query)
diff --git a/Emby.AutoOrganize/Core/MovieFileOrganizer.cs b/Emby.AutoOrganize/Core/MovieFileOrganizer.cs
index 83d32d8..551f457 100644
--- a/Emby.AutoOrganize/Core/MovieFileOrganizer.cs
+++ b/Emby.AutoOrganize/Core/MovieFileOrganizer.cs
@@ -107,7 +107,7 @@ await OrganizeMovie(path,
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = ex.Message;
- _logger.ErrorException("Error organizing file", ex);
+ _logger.ErrorException("Error organizing file {0}", ex, path);
}
_organizationService.SaveResult(result, CancellationToken.None);
@@ -197,6 +197,7 @@ public FileOrganizationResult OrganizeWithCorrection(MovieFileOrganizationReques
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = ex.Message;
+ _logger.ErrorException("Error organizing file {0}", ex, result.OriginalPath);
}
return result;
@@ -378,7 +379,7 @@ private void PerformFileSorting(MovieFileOrganizationOptions options, FileOrgani
}
catch (Exception ex)
{
- _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath);
+ _logger.ErrorException("Error deleting file {0}", ex, result.OriginalPath);
}
}
}
diff --git a/Emby.AutoOrganize/Core/MovieFolderOrganizer.cs b/Emby.AutoOrganize/Core/MovieFolderOrganizer.cs
index bd86f81..e5144d9 100644
--- a/Emby.AutoOrganize/Core/MovieFolderOrganizer.cs
+++ b/Emby.AutoOrganize/Core/MovieFolderOrganizer.cs
@@ -109,7 +109,7 @@ public async Task Organize(MovieFileOrganizationOptions options, CancellationTok
}
catch (Exception ex)
{
- _logger.ErrorException("Error organizing episode {0}", ex, file.FullName);
+ _logger.ErrorException("Error organizing file {0}", ex, file.FullName);
}
numComplete++;
diff --git a/Emby.AutoOrganize/Core/TvFolderOrganizer.cs b/Emby.AutoOrganize/Core/TvFolderOrganizer.cs
index 701e06b..42e0bea 100644
--- a/Emby.AutoOrganize/Core/TvFolderOrganizer.cs
+++ b/Emby.AutoOrganize/Core/TvFolderOrganizer.cs
@@ -110,7 +110,7 @@ public async Task Organize(TvFileOrganizationOptions options,
}
catch (Exception ex)
{
- _logger.ErrorException("Error organizing episode {0}", ex, file.FullName);
+ _logger.ErrorException("Error organizing file {0}", ex, file.FullName);
}
numComplete++;
diff --git a/Emby.AutoOrganize/Emby.AutoOrganize.csproj b/Emby.AutoOrganize/Emby.AutoOrganize.csproj
index a6541aa..df9db4f 100644
--- a/Emby.AutoOrganize/Emby.AutoOrganize.csproj
+++ b/Emby.AutoOrganize/Emby.AutoOrganize.csproj
@@ -2,8 +2,8 @@
netstandard2.0;
- 1.6.0.0
- 1.6.0.0
+ 1.6.1.0
+ 1.6.1.0
diff --git a/Emby.AutoOrganize/Model/TvFileOrganizationOptions.cs b/Emby.AutoOrganize/Model/TvFileOrganizationOptions.cs
index ed68930..05ff6e9 100644
--- a/Emby.AutoOrganize/Model/TvFileOrganizationOptions.cs
+++ b/Emby.AutoOrganize/Model/TvFileOrganizationOptions.cs
@@ -15,8 +15,12 @@ public class TvFileOrganizationOptions
public string EpisodeNamePattern { get; set; }
public string MultiEpisodeNamePattern { get; set; }
+ public bool AllowTBA { get; set; }
+
public bool OverwriteExistingEpisodes { get; set; }
+ public bool SingleEpisodeVersion { get; set; }
+
public bool DeleteEmptyFolders { get; set; }
public bool ExtendedClean { get; set; }
@@ -45,6 +49,9 @@ public TvFileOrganizationOptions()
CopyOriginalFile = false;
+ AllowTBA = true;
+ SingleEpisodeVersion = false;
+
ExtendedClean = false;
}
}
diff --git a/Emby.AutoOrganize/thumb.jpg b/Emby.AutoOrganize/thumb.jpg
index dd0f8d7..442fe81 100644
Binary files a/Emby.AutoOrganize/thumb.jpg and b/Emby.AutoOrganize/thumb.jpg differ