2014-01-23 18:46:33 +00:00
using MediaBrowser.Common.IO ;
2014-01-22 17:05:06 +00:00
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Entities.TV ;
using MediaBrowser.Controller.FileOrganization ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Providers ;
using MediaBrowser.Model.Entities ;
2015-02-19 04:37:44 +00:00
using MediaBrowser.Model.Extensions ;
2014-01-22 17:05:06 +00:00
using MediaBrowser.Model.FileOrganization ;
using MediaBrowser.Model.Logging ;
2015-02-19 04:37:44 +00:00
using MediaBrowser.Server.Implementations.Library ;
using MediaBrowser.Server.Implementations.Logging ;
2014-01-23 18:46:33 +00:00
using System ;
using System.Collections.Generic ;
2014-01-22 17:05:06 +00:00
using System.Globalization ;
2014-01-23 18:46:33 +00:00
using System.IO ;
using System.Linq ;
using System.Threading ;
2014-01-22 17:05:06 +00:00
using System.Threading.Tasks ;
2015-10-04 04:23:11 +00:00
using CommonIO ;
2014-01-22 17:05:06 +00:00
namespace MediaBrowser.Server.Implementations.FileOrganization
{
public class EpisodeFileOrganizer
{
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-01-23 18:46:33 +00:00
private readonly CultureInfo _usCulture = new CultureInfo ( "en-US" ) ;
2014-01-22 17:05:06 +00:00
2014-02-20 04:53:15 +00:00
public EpisodeFileOrganizer ( IFileOrganizationService organizationService , IServerConfigurationManager config , IFileSystem fileSystem , ILogger logger , ILibraryManager libraryManager , ILibraryMonitor libraryMonitor , IProviderManager providerManager )
2014-01-22 17:05:06 +00:00
{
_organizationService = organizationService ;
_config = config ;
_fileSystem = fileSystem ;
_logger = logger ;
_libraryManager = libraryManager ;
2014-01-28 21:25:10 +00:00
_libraryMonitor = libraryMonitor ;
2014-02-20 04:53:15 +00:00
_providerManager = providerManager ;
2014-01-22 17:05:06 +00:00
}
2015-08-22 19:46:55 +00:00
public Task < FileOrganizationResult > OrganizeEpisodeFile ( string path , CancellationToken cancellationToken )
{
var options = _config . GetAutoOrganizeOptions ( ) . TvOptions ;
return OrganizeEpisodeFile ( path , options , false , cancellationToken ) ;
}
2014-02-20 04:53:15 +00:00
public async Task < FileOrganizationResult > OrganizeEpisodeFile ( string path , TvFileOrganizationOptions options , bool overwriteExisting , CancellationToken cancellationToken )
2014-01-22 17:05:06 +00:00
{
_logger . Info ( "Sorting file {0}" , path ) ;
var result = new FileOrganizationResult
{
Date = DateTime . UtcNow ,
OriginalPath = path ,
OriginalFileName = Path . GetFileName ( path ) ,
2014-02-19 16:24:06 +00:00
Type = FileOrganizerType . Episode ,
FileSize = new FileInfo ( path ) . Length
2014-01-22 17:05:06 +00:00
} ;
2015-08-22 19:46:55 +00:00
var namingOptions = ( ( LibraryManager ) _libraryManager ) . GetNamingOptions ( ) ;
2015-01-17 04:38:24 +00:00
var resolver = new Naming . TV . EpisodeResolver ( namingOptions , new PatternsLogger ( ) ) ;
2014-12-19 04:20:07 +00:00
2015-04-29 18:48:34 +00:00
var episodeInfo = resolver . Resolve ( path , false ) ? ?
2014-12-19 04:20:07 +00:00
new Naming . TV . EpisodeInfo ( ) ;
var seriesName = episodeInfo . SeriesName ;
2014-01-22 17:05:06 +00:00
if ( ! string . IsNullOrEmpty ( seriesName ) )
{
2016-01-18 05:30:50 +00:00
var seasonNumber = episodeInfo . SeasonNumber ;
2016-01-28 18:29:41 +00:00
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 ,
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 ) ;
}
2014-01-22 17:05:06 +00:00
}
else
{
var msg = string . Format ( "Unable to determine series name from {0}" , path ) ;
result . Status = FileSortingStatus . Failure ;
result . StatusMessage = msg ;
_logger . Warn ( msg ) ;
}
2014-02-19 16:24:06 +00:00
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 & & result . Status ! = FileSortingStatus . Success )
{
return previousResult ;
}
}
2014-01-22 17:05:06 +00:00
await _organizationService . SaveResult ( result , CancellationToken . None ) . ConfigureAwait ( false ) ;
return result ;
}
2014-02-20 04:53:15 +00:00
public async Task < FileOrganizationResult > OrganizeWithCorrection ( EpisodeFileOrganizationRequest request , TvFileOrganizationOptions options , CancellationToken cancellationToken )
2014-01-22 17:05:06 +00:00
{
var result = _organizationService . GetResult ( request . ResultId ) ;
var series = ( Series ) _libraryManager . GetItemById ( new Guid ( request . SeriesId ) ) ;
2016-01-28 18:29:41 +00:00
await OrganizeEpisode ( result . OriginalPath ,
series ,
request . SeasonNumber ,
request . EpisodeNumber ,
request . EndingEpisodeNumber ,
null ,
options ,
true ,
result ,
cancellationToken ) . ConfigureAwait ( false ) ;
2014-01-22 17:05:06 +00:00
await _organizationService . SaveResult ( result , CancellationToken . None ) . ConfigureAwait ( false ) ;
return result ;
}
2016-01-28 18:29:41 +00:00
private Task OrganizeEpisode ( string sourcePath ,
string seriesName ,
int? seasonNumber ,
int? episodeNumber ,
int? endingEpiosdeNumber ,
DateTime ? premiereDate ,
TvFileOrganizationOptions options ,
bool overwriteExisting ,
FileOrganizationResult result ,
CancellationToken cancellationToken )
2014-01-22 17:05:06 +00:00
{
var series = GetMatchingSeries ( seriesName , result ) ;
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 ) ;
2014-02-20 04:53:15 +00:00
return Task . FromResult ( true ) ;
2014-01-22 17:05:06 +00:00
}
2016-01-28 18:29:41 +00:00
if ( ! series . ProviderIds . Any ( ) )
{
var msg = string . Format ( "Series has not yet been identified: {0}. If you just added the series, please run a library scan or use the identify feature to identify it." , 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 ,
result ,
cancellationToken ) ;
2014-01-22 17:05:06 +00:00
}
2016-01-28 18:29:41 +00:00
private async Task OrganizeEpisode ( string sourcePath ,
Series series ,
int? seasonNumber ,
int? episodeNumber ,
int? endingEpiosdeNumber ,
DateTime ? premiereDate ,
TvFileOrganizationOptions options ,
bool overwriteExisting ,
FileOrganizationResult result ,
CancellationToken cancellationToken )
2014-01-22 17:05:06 +00:00
{
_logger . Info ( "Sorting file {0} into series {1}" , sourcePath , series . Path ) ;
// Proceed to sort the file
2016-01-28 18:29:41 +00:00
var newPath = await GetNewPath ( sourcePath , series , seasonNumber , episodeNumber , endingEpiosdeNumber , premiereDate , options , cancellationToken ) . ConfigureAwait ( false ) ;
2014-01-22 17:05:06 +00:00
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 ;
2015-09-30 05:15:25 +00:00
var fileExists = _fileSystem . FileExists ( result . TargetPath ) ;
2014-01-23 18:46:33 +00:00
var otherDuplicatePaths = GetOtherDuplicatePaths ( result . TargetPath , series , seasonNumber , episodeNumber , endingEpiosdeNumber ) ;
2014-01-22 17:05:06 +00:00
2014-03-06 04:44:29 +00:00
if ( ! overwriteExisting )
2014-01-22 17:05:06 +00:00
{
2014-03-12 19:56:12 +00:00
if ( options . CopyOriginalFile & & fileExists & & IsSameEpisode ( sourcePath , newPath ) )
2014-03-06 04:44:29 +00:00
{
2014-03-12 19:56:12 +00:00
_logger . Info ( "File {0} already copied to new path {1}, stopping organization" , sourcePath , newPath ) ;
2014-03-06 04:44:29 +00:00
result . Status = FileSortingStatus . SkippedExisting ;
result . StatusMessage = string . Empty ;
return ;
}
2014-03-22 18:25:03 +00:00
2014-03-12 19:56:12 +00:00
if ( fileExists | | otherDuplicatePaths . Count > 0 )
2014-03-06 04:44:29 +00:00
{
result . Status = FileSortingStatus . SkippedExisting ;
result . StatusMessage = string . Empty ;
2014-03-12 19:56:12 +00:00
result . DuplicatePaths = otherDuplicatePaths ;
2014-03-06 04:44:29 +00:00
return ;
}
2014-01-22 17:05:06 +00:00
}
PerformFileSorting ( options , result ) ;
2014-01-23 18:46:33 +00:00
if ( overwriteExisting )
{
2015-02-19 04:37:44 +00:00
var hasRenamedFiles = false ;
2014-01-23 18:46:33 +00:00
foreach ( var path in otherDuplicatePaths )
{
_logger . Debug ( "Removing duplicate episode {0}" , path ) ;
2014-01-28 21:25:10 +00:00
_libraryMonitor . ReportFileSystemChangeBeginning ( path ) ;
2015-02-19 17:46:18 +00:00
var renameRelatedFiles = ! hasRenamedFiles & &
string . Equals ( Path . GetDirectoryName ( path ) , Path . GetDirectoryName ( result . TargetPath ) , StringComparison . OrdinalIgnoreCase ) ;
2015-02-19 04:37:44 +00:00
if ( renameRelatedFiles )
{
hasRenamedFiles = true ;
}
2014-01-23 18:46:33 +00:00
try
{
2015-02-19 04:37:44 +00:00
DeleteLibraryFile ( path , renameRelatedFiles , result . TargetPath ) ;
2014-01-23 18:46:33 +00:00
}
catch ( IOException ex )
{
_logger . ErrorException ( "Error removing duplicate episode" , ex , path ) ;
}
2014-01-28 21:25:10 +00:00
finally
{
_libraryMonitor . ReportFileSystemChangeComplete ( path , true ) ;
}
2014-01-23 18:46:33 +00:00
}
}
2014-01-22 17:05:06 +00:00
}
2015-02-19 04:37:44 +00:00
private void DeleteLibraryFile ( string path , bool renameRelatedFiles , string targetPath )
2015-02-16 00:33:06 +00:00
{
_fileSystem . DeleteFile ( path ) ;
2015-02-19 04:37:44 +00:00
if ( ! renameRelatedFiles )
{
return ;
}
2015-02-16 00:33:06 +00:00
// Now find other files
2015-02-19 04:37:44 +00:00
var originalFilenameWithoutExtension = Path . GetFileNameWithoutExtension ( path ) ;
var directory = Path . GetDirectoryName ( path ) ;
if ( ! string . IsNullOrWhiteSpace ( originalFilenameWithoutExtension ) & & ! string . IsNullOrWhiteSpace ( directory ) )
{
// Get all related files, e.g. metadata, images, etc
2015-09-24 17:50:49 +00:00
var files = _fileSystem . GetFilePaths ( directory )
2015-02-19 04:37:44 +00:00
. Where ( i = > ( Path . GetFileNameWithoutExtension ( i ) ? ? string . Empty ) . StartsWith ( originalFilenameWithoutExtension , StringComparison . OrdinalIgnoreCase ) )
. ToList ( ) ;
var targetFilenameWithoutExtension = Path . GetFileNameWithoutExtension ( targetPath ) ;
2015-08-22 19:46:55 +00:00
2015-02-19 04:37:44 +00:00
foreach ( var file in files )
{
directory = Path . GetDirectoryName ( file ) ;
var filename = Path . GetFileName ( file ) ;
filename = filename . Replace ( originalFilenameWithoutExtension , targetFilenameWithoutExtension ,
StringComparison . OrdinalIgnoreCase ) ;
var destination = Path . Combine ( directory , filename ) ;
2015-09-30 05:15:25 +00:00
_fileSystem . MoveFile ( file , destination ) ;
2015-02-19 04:37:44 +00:00
}
}
2015-02-16 00:33:06 +00:00
}
2016-01-28 18:29:41 +00:00
private List < string > GetOtherDuplicatePaths ( string targetPath ,
Series series ,
int? seasonNumber ,
int? episodeNumber ,
int? endingEpisodeNumber )
2014-01-22 17:05:06 +00:00
{
2016-01-28 18:29:41 +00:00
// TODO: Support date-naming?
if ( ! seasonNumber . HasValue | | episodeNumber . HasValue )
{
return new List < string > ( ) ;
}
2016-01-18 05:30:50 +00:00
2015-01-25 06:34:50 +00:00
var episodePaths = series . GetRecursiveChildren ( )
2014-01-23 18:46:33 +00:00
. OfType < Episode > ( )
. Where ( i = >
{
var locationType = i . LocationType ;
// Must be file system based and match exactly
if ( locationType ! = LocationType . Remote & &
locationType ! = LocationType . Virtual & &
i . ParentIndexNumber . HasValue & &
i . ParentIndexNumber . Value = = seasonNumber & &
i . IndexNumber . HasValue & &
i . IndexNumber . Value = = episodeNumber )
{
2014-01-22 17:05:06 +00:00
2014-01-23 18:46:33 +00:00
if ( endingEpisodeNumber . HasValue | | i . IndexNumberEnd . HasValue )
{
return endingEpisodeNumber . HasValue & & i . IndexNumberEnd . HasValue & &
endingEpisodeNumber . Value = = i . IndexNumberEnd . Value ;
}
2014-01-22 17:05:06 +00:00
2014-01-23 18:46:33 +00:00
return true ;
}
return false ;
} )
. Select ( i = > i . Path )
. ToList ( ) ;
var folder = Path . GetDirectoryName ( targetPath ) ;
2014-07-26 17:30:15 +00:00
var targetFileNameWithoutExtension = _fileSystem . GetFileNameWithoutExtension ( targetPath ) ;
2014-12-19 04:20:07 +00:00
2014-02-25 15:40:16 +00:00
try
{
2015-09-24 17:50:49 +00:00
var filesOfOtherExtensions = _fileSystem . GetFilePaths ( folder )
2014-11-16 20:44:08 +00:00
. Where ( i = > _libraryManager . IsVideoFile ( i ) & & string . Equals ( _fileSystem . GetFileNameWithoutExtension ( i ) , targetFileNameWithoutExtension , StringComparison . OrdinalIgnoreCase ) ) ;
2014-01-23 18:46:33 +00:00
2014-02-25 15:40:16 +00:00
episodePaths . AddRange ( filesOfOtherExtensions ) ;
}
catch ( DirectoryNotFoundException )
{
// No big deal. Maybe the season folder doesn't already exist.
}
2014-01-23 18:46:33 +00:00
return episodePaths . Where ( i = > ! string . Equals ( i , targetPath , StringComparison . OrdinalIgnoreCase ) )
. Distinct ( StringComparer . OrdinalIgnoreCase )
. ToList ( ) ;
2014-01-22 17:05:06 +00:00
}
private void PerformFileSorting ( TvFileOrganizationOptions options , FileOrganizationResult result )
{
2014-01-28 21:25:10 +00:00
_libraryMonitor . ReportFileSystemChangeBeginning ( result . TargetPath ) ;
2014-01-22 17:05:06 +00:00
2015-09-30 05:15:25 +00:00
_fileSystem . CreateDirectory ( Path . GetDirectoryName ( result . TargetPath ) ) ;
2014-01-22 17:05:06 +00:00
2015-09-30 05:15:25 +00:00
var targetAlreadyExists = _fileSystem . FileExists ( result . TargetPath ) ;
2014-01-23 18:46:33 +00:00
2014-01-22 17:05:06 +00:00
try
{
2015-02-16 00:33:06 +00:00
if ( targetAlreadyExists | | options . CopyOriginalFile )
2014-01-22 17:05:06 +00:00
{
2015-09-30 05:15:25 +00:00
_fileSystem . CopyFile ( result . OriginalPath , result . TargetPath , true ) ;
2014-01-22 17:05:06 +00:00
}
else
{
2015-09-30 05:15:25 +00:00
_fileSystem . MoveFile ( result . OriginalPath , result . TargetPath ) ;
2014-01-22 17:05:06 +00:00
}
result . Status = FileSortingStatus . Success ;
result . StatusMessage = string . Empty ;
}
catch ( Exception ex )
{
var errorMsg = string . Format ( "Failed to move file from {0} to {1}" , result . OriginalPath , result . TargetPath ) ;
result . Status = FileSortingStatus . Failure ;
result . StatusMessage = errorMsg ;
_logger . ErrorException ( errorMsg , ex ) ;
return ;
}
finally
{
2014-01-28 21:25:10 +00:00
_libraryMonitor . ReportFileSystemChangeComplete ( result . TargetPath , true ) ;
2014-01-22 17:05:06 +00:00
}
2015-02-16 00:33:06 +00:00
if ( targetAlreadyExists & & ! options . CopyOriginalFile )
2014-01-22 17:05:06 +00:00
{
try
{
2015-01-13 03:46:44 +00:00
_fileSystem . DeleteFile ( result . OriginalPath ) ;
2014-01-22 17:05:06 +00:00
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error deleting {0}" , ex , result . OriginalPath ) ;
}
}
}
private Series GetMatchingSeries ( string seriesName , FileOrganizationResult result )
{
2014-11-16 22:46:01 +00:00
var parsedName = _libraryManager . ParseName ( seriesName ) ;
var yearInName = parsedName . Year ;
var nameWithoutYear = parsedName . Name ;
2014-01-22 17:05:06 +00:00
result . ExtractedName = nameWithoutYear ;
result . ExtractedYear = yearInName ;
2015-01-25 06:34:50 +00:00
return _libraryManager . RootFolder . GetRecursiveChildren ( i = > i is Series )
. Cast < Series > ( )
2014-01-22 17:05:06 +00:00
. Select ( i = > NameUtils . GetMatchScore ( nameWithoutYear , yearInName , i ) )
. Where ( i = > i . Item2 > 0 )
. OrderByDescending ( i = > i . Item2 )
. Select ( i = > i . Item1 )
. FirstOrDefault ( ) ;
}
/// <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>
2016-01-28 18:29:41 +00:00
private async Task < string > GetNewPath ( string sourcePath ,
Series series ,
int? seasonNumber ,
int? episodeNumber ,
int? endingEpisodeNumber ,
DateTime ? premiereDate ,
TvFileOrganizationOptions options ,
CancellationToken cancellationToken )
2014-01-22 17:05:06 +00:00
{
2014-02-20 04:53:15 +00:00
var episodeInfo = new EpisodeInfo
{
IndexNumber = episodeNumber ,
IndexNumberEnd = endingEpisodeNumber ,
MetadataCountryCode = series . GetPreferredMetadataCountryCode ( ) ,
MetadataLanguage = series . GetPreferredMetadataLanguage ( ) ,
ParentIndexNumber = seasonNumber ,
2016-01-18 05:30:50 +00:00
SeriesProviderIds = series . ProviderIds ,
2016-01-28 18:29:41 +00:00
PremiereDate = premiereDate
2014-02-20 04:53:15 +00:00
} ;
2014-01-22 17:05:06 +00:00
2014-02-20 04:53:15 +00:00
var searchResults = await _providerManager . GetRemoteSearchResults < Episode , EpisodeInfo > ( new RemoteSearchQuery < EpisodeInfo >
{
SearchInfo = episodeInfo
} , cancellationToken ) . ConfigureAwait ( false ) ;
var episode = searchResults . FirstOrDefault ( ) ;
2016-01-28 18:29:41 +00:00
2016-01-18 05:30:50 +00:00
string episodeName = string . Empty ;
2014-02-20 04:53:15 +00:00
if ( episode = = null )
2014-01-22 17:05:06 +00:00
{
2016-01-18 05:30:50 +00:00
var msg = string . Format ( "No provider metadata found for {0} season {1} episode {2}" , series . Name , seasonNumber , episodeNumber ) ;
_logger . Warn ( msg ) ;
//throw new Exception(msg);
2014-01-22 17:05:06 +00:00
}
2016-01-18 05:30:50 +00:00
else
{
episodeName = episode . Name ;
2016-01-28 18:29:41 +00:00
}
2016-01-18 05:30:50 +00:00
2016-01-28 18:29:41 +00:00
seasonNumber = seasonNumber ? ? episode . ParentIndexNumber ;
episodeNumber = episodeNumber ? ? episode . IndexNumber ;
2014-01-22 17:05:06 +00:00
2016-01-18 05:30:50 +00:00
var newPath = GetSeasonFolderPath ( series , seasonNumber . Value , options ) ;
2014-01-22 17:05:06 +00:00
2015-10-02 18:44:30 +00:00
// MAX_PATH - trailing <NULL> charachter - drive component: 260 - 1 - 3 = 256
// Usually newPath would include the drive component, but use 256 to be sure
var maxFilenameLength = 256 - newPath . Length ;
2014-01-22 17:05:06 +00:00
2015-10-02 18:44:30 +00:00
if ( ! newPath . EndsWith ( @"\" ) )
2015-09-30 05:15:25 +00:00
{
2015-10-02 18:44:30 +00:00
// Remove 1 for missing backslash combining path and filename
maxFilenameLength - - ;
}
// Remove additional 4 chars to prevent PathTooLongException for downloaded subtitles (eg. filename.ext.eng.srt)
maxFilenameLength - = 4 ;
2015-09-30 05:15:25 +00:00
2016-01-18 05:30:50 +00:00
var episodeFileName = GetEpisodeFileName ( sourcePath , series . Name , seasonNumber . Value , episodeNumber . Value , endingEpisodeNumber , episodeName , options , maxFilenameLength ) ;
2015-10-02 18:44:30 +00:00
if ( string . IsNullOrEmpty ( episodeFileName ) )
{
// cause failure
return string . Empty ;
2015-09-30 05:15:25 +00:00
}
2015-10-02 18:44:30 +00:00
newPath = Path . Combine ( newPath , episodeFileName ) ;
2014-01-22 17:05:06 +00:00
return newPath ;
}
/// <summary>
/// Gets the season folder path.
/// </summary>
/// <param name="series">The series.</param>
/// <param name="seasonNumber">The season number.</param>
/// <param name="options">The options.</param>
/// <returns>System.String.</returns>
private string GetSeasonFolderPath ( Series series , int seasonNumber , TvFileOrganizationOptions options )
{
// If there's already a season folder, use that
var season = series
2015-01-25 06:34:50 +00:00
. GetRecursiveChildren ( i = > i is Season & & i . LocationType = = LocationType . FileSystem & & i . IndexNumber . HasValue & & i . IndexNumber . Value = = seasonNumber )
. FirstOrDefault ( ) ;
2014-01-22 17:05:06 +00:00
if ( season ! = null )
{
return season . Path ;
}
var path = series . Path ;
if ( series . ContainsEpisodesWithoutSeasonFolders )
{
return path ;
}
if ( seasonNumber = = 0 )
{
return Path . Combine ( path , _fileSystem . GetValidFilename ( options . SeasonZeroFolderName ) ) ;
}
var seasonFolderName = options . SeasonFolderPattern
. Replace ( "%s" , seasonNumber . ToString ( _usCulture ) )
. Replace ( "%0s" , seasonNumber . ToString ( "00" , _usCulture ) )
. Replace ( "%00s" , seasonNumber . ToString ( "000" , _usCulture ) ) ;
return Path . Combine ( path , _fileSystem . GetValidFilename ( seasonFolderName ) ) ;
}
2015-10-02 18:44:30 +00:00
private string GetEpisodeFileName ( string sourcePath , string seriesName , int seasonNumber , int episodeNumber , int? endingEpisodeNumber , string episodeTitle , TvFileOrganizationOptions options , int? maxLength )
2014-01-22 17:05:06 +00:00
{
2014-01-27 04:29:01 +00:00
seriesName = _fileSystem . GetValidFilename ( seriesName ) . Trim ( ) ;
2015-11-25 17:13:04 +00:00
2016-01-18 05:30:50 +00:00
if ( string . IsNullOrEmpty ( episodeTitle ) )
2015-11-25 17:13:04 +00:00
{
episodeTitle = string . Empty ;
}
else
{
episodeTitle = _fileSystem . GetValidFilename ( episodeTitle ) . Trim ( ) ;
}
2014-01-22 17:05:06 +00:00
var sourceExtension = ( Path . GetExtension ( sourcePath ) ? ? string . Empty ) . TrimStart ( '.' ) ;
var pattern = endingEpisodeNumber . HasValue ? options . MultiEpisodeNamePattern : options . EpisodeNamePattern ;
var result = pattern . Replace ( "%sn" , seriesName )
. Replace ( "%s.n" , seriesName . Replace ( " " , "." ) )
. Replace ( "%s_n" , seriesName . Replace ( " " , "_" ) )
. Replace ( "%s" , seasonNumber . ToString ( _usCulture ) )
. Replace ( "%0s" , seasonNumber . ToString ( "00" , _usCulture ) )
. Replace ( "%00s" , seasonNumber . ToString ( "000" , _usCulture ) )
. Replace ( "%ext" , sourceExtension )
2015-09-21 13:24:33 +00:00
. Replace ( "%en" , "%#1" )
. Replace ( "%e.n" , "%#2" )
. Replace ( "%e_n" , "%#3" ) ;
2014-01-22 17:05:06 +00:00
if ( endingEpisodeNumber . HasValue )
{
result = result . Replace ( "%ed" , endingEpisodeNumber . Value . ToString ( _usCulture ) )
. Replace ( "%0ed" , endingEpisodeNumber . Value . ToString ( "00" , _usCulture ) )
. Replace ( "%00ed" , endingEpisodeNumber . Value . ToString ( "000" , _usCulture ) ) ;
}
2015-09-21 13:24:33 +00:00
result = result . Replace ( "%e" , episodeNumber . ToString ( _usCulture ) )
2014-01-22 17:05:06 +00:00
. Replace ( "%0e" , episodeNumber . ToString ( "00" , _usCulture ) )
. Replace ( "%00e" , episodeNumber . ToString ( "000" , _usCulture ) ) ;
2015-09-21 13:24:33 +00:00
2015-10-02 18:44:30 +00:00
if ( maxLength . HasValue & & result . Contains ( "%#" ) )
2015-09-21 13:24:33 +00:00
{
2015-10-02 18:44:30 +00:00
// Substract 3 for the temp token length (%#1, %#2 or %#3)
int maxRemainingTitleLength = maxLength . Value - result . Length + 3 ;
string shortenedEpisodeTitle = string . Empty ;
if ( maxRemainingTitleLength > 5 )
{
// A title with fewer than 5 letters wouldn't be of much value
shortenedEpisodeTitle = episodeTitle . Substring ( 0 , Math . Min ( maxRemainingTitleLength , episodeTitle . Length ) ) ;
}
result = result . Replace ( "%#1" , shortenedEpisodeTitle )
. Replace ( "%#2" , shortenedEpisodeTitle . Replace ( " " , "." ) )
. Replace ( "%#3" , shortenedEpisodeTitle . Replace ( " " , "_" ) ) ;
2015-09-21 13:24:33 +00:00
}
2015-10-02 18:44:30 +00:00
if ( maxLength . HasValue & & result . Length > maxLength . Value )
{
// There may be cases where reducing the title length may still not be sufficient to
// stay below maxLength
var msg = string . Format ( "Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit" , maxLength ) ;
_logger . Warn ( msg ) ;
return string . Empty ;
}
2015-09-21 13:24:33 +00:00
2015-10-02 18:44:30 +00:00
return result ;
2014-01-22 17:05:06 +00:00
}
2014-03-06 04:44:29 +00:00
private bool IsSameEpisode ( string sourcePath , string newPath )
{
2014-03-22 18:25:03 +00:00
try
{
2014-12-23 03:58:14 +00:00
var sourceFileInfo = new FileInfo ( sourcePath ) ;
var destinationFileInfo = new FileInfo ( newPath ) ;
2014-03-22 18:25:03 +00:00
if ( sourceFileInfo . Length = = destinationFileInfo . Length )
2014-03-06 04:44:29 +00:00
{
2014-03-22 18:25:03 +00:00
return true ;
2014-03-06 04:44:29 +00:00
}
2014-03-22 18:25:03 +00:00
}
catch ( FileNotFoundException )
{
2014-03-06 04:44:29 +00:00
return false ;
2014-03-22 18:25:03 +00:00
}
2014-12-23 03:58:14 +00:00
catch ( DirectoryNotFoundException )
{
return false ;
}
2014-03-06 04:44:29 +00:00
2014-03-22 18:25:03 +00:00
return false ;
2014-03-06 04:44:29 +00:00
}
2014-01-22 17:05:06 +00:00
}
}