2018-02-15 12:39:01 +00:00
using System ;
2015-02-21 19:49:56 +00:00
using System.Collections.Generic ;
2014-08-11 04:20:06 +00:00
using System.Diagnostics ;
2014-03-25 23:25:45 +00:00
using System.IO ;
2011-06-20 01:59:31 +00:00
using System.Linq ;
2014-11-07 04:02:07 +00:00
using System.Text.RegularExpressions ;
2011-06-20 01:59:31 +00:00
using NLog ;
2014-01-06 06:20:08 +00:00
using NzbDrone.Common.Disk ;
2015-01-04 07:07:25 +00:00
using NzbDrone.Common.Extensions ;
2014-07-23 23:43:54 +00:00
using NzbDrone.Common.Instrumentation.Extensions ;
2013-11-19 06:25:02 +00:00
using NzbDrone.Core.Configuration ;
2013-05-13 00:36:23 +00:00
using NzbDrone.Core.MediaFiles.Commands ;
2014-03-08 18:35:48 +00:00
using NzbDrone.Core.MediaFiles.Events ;
2021-11-28 21:42:44 +00:00
using NzbDrone.Core.MediaFiles.MediaInfo ;
2019-12-22 22:08:53 +00:00
using NzbDrone.Core.MediaFiles.MovieImport ;
2013-09-14 06:42:09 +00:00
using NzbDrone.Core.Messaging.Commands ;
using NzbDrone.Core.Messaging.Events ;
2018-03-14 20:41:36 +00:00
using NzbDrone.Core.Movies ;
2019-07-10 03:14:53 +00:00
using NzbDrone.Core.RootFolders ;
2011-06-20 01:59:31 +00:00
2013-05-13 00:36:23 +00:00
namespace NzbDrone.Core.MediaFiles
2011-06-20 01:59:31 +00:00
{
2013-04-15 05:27:32 +00:00
public interface IDiskScanService
{
2017-01-02 17:05:55 +00:00
void Scan ( Movie movie ) ;
2013-04-15 05:27:32 +00:00
string [ ] GetVideoFiles ( string path , bool allDirectories = true ) ;
2016-09-18 18:04:56 +00:00
string [ ] GetNonVideoFiles ( string path , bool allDirectories = true ) ;
2020-09-05 05:23:24 +00:00
List < string > FilterPaths ( string basePath , IEnumerable < string > paths , bool filterExtras = true ) ;
2013-04-15 05:27:32 +00:00
}
2013-07-13 20:21:34 +00:00
public class DiskScanService :
IDiskScanService ,
2018-03-14 20:41:36 +00:00
IExecute < RescanMovieCommand >
2011-06-20 01:59:31 +00:00
{
2013-05-10 23:53:50 +00:00
private readonly IDiskProvider _diskProvider ;
2013-07-06 21:47:49 +00:00
private readonly IMakeImportDecision _importDecisionMaker ;
2017-01-09 03:16:14 +00:00
private readonly IImportApprovedMovie _importApprovedMovies ;
2013-11-19 06:25:02 +00:00
private readonly IConfigService _configService ;
2019-07-10 03:14:53 +00:00
private readonly IMovieService _movieService ;
2021-11-28 21:42:44 +00:00
private readonly IMediaFileService _mediaFileService ;
2015-01-16 00:30:09 +00:00
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService ;
2019-07-10 03:14:53 +00:00
private readonly IRootFolderService _rootFolderService ;
2021-11-28 21:42:44 +00:00
private readonly IUpdateMediaInfo _updateMediaInfoService ;
2014-03-08 18:35:48 +00:00
private readonly IEventAggregator _eventAggregator ;
2013-08-18 04:28:34 +00:00
private readonly Logger _logger ;
2011-06-20 01:59:31 +00:00
2013-07-06 21:47:49 +00:00
public DiskScanService ( IDiskProvider diskProvider ,
2014-03-08 18:35:48 +00:00
IMakeImportDecision importDecisionMaker ,
2017-01-09 03:16:14 +00:00
IImportApprovedMovie importApprovedMovies ,
2014-03-08 18:35:48 +00:00
IConfigService configService ,
2019-07-10 03:14:53 +00:00
IMovieService movieService ,
2021-11-28 21:42:44 +00:00
IMediaFileService mediaFileService ,
2015-01-16 00:30:09 +00:00
IMediaFileTableCleanupService mediaFileTableCleanupService ,
2019-07-10 03:14:53 +00:00
IRootFolderService rootFolderService ,
2021-11-28 21:42:44 +00:00
IUpdateMediaInfo updateMediaInfoService ,
2014-03-08 18:35:48 +00:00
IEventAggregator eventAggregator ,
Logger logger )
2011-06-20 01:59:31 +00:00
{
_diskProvider = diskProvider ;
2013-07-06 21:47:49 +00:00
_importDecisionMaker = importDecisionMaker ;
2017-01-09 03:16:14 +00:00
_importApprovedMovies = importApprovedMovies ;
2013-11-19 06:25:02 +00:00
_configService = configService ;
2019-07-10 03:14:53 +00:00
_movieService = movieService ;
2021-11-28 21:42:44 +00:00
_mediaFileService = mediaFileService ;
2015-01-16 00:30:09 +00:00
_mediaFileTableCleanupService = mediaFileTableCleanupService ;
2019-07-10 03:14:53 +00:00
_rootFolderService = rootFolderService ;
2021-11-28 21:42:44 +00:00
_updateMediaInfoService = updateMediaInfoService ;
2014-03-08 18:35:48 +00:00
_eventAggregator = eventAggregator ;
2013-08-18 04:28:34 +00:00
_logger = logger ;
2011-06-20 01:59:31 +00:00
}
2020-07-22 01:40:08 +00:00
private static readonly Regex ExcludedExtrasSubFolderRegex = new Regex ( @"(?:\\|\/|^)(?:extras|extrafanart|behind the scenes|deleted scenes|featurettes|interviews|scenes|sample[s]?|shorts|trailers)(?:\\|\/)" , RegexOptions . Compiled | RegexOptions . IgnoreCase ) ;
2020-07-02 01:48:13 +00:00
private static readonly Regex ExcludedSubFoldersRegex = new Regex ( @"(?:\\|\/|^)(?:@eadir|\.@__thumb|plex versions|\.[^\\/]+)(?:\\|\/)" , RegexOptions . Compiled | RegexOptions . IgnoreCase ) ;
2020-07-22 01:53:31 +00:00
private static readonly Regex ExcludedExtraFilesRegex = new Regex ( @"(-(trailer|other|behindthescenes|deleted|featurette|interview|scene|short)\.[^.]+$)" , RegexOptions . Compiled | RegexOptions . IgnoreCase ) ;
2019-07-10 03:14:53 +00:00
private static readonly Regex ExcludedFilesRegex = new Regex ( @"^\._|^Thumbs\.db$" , RegexOptions . Compiled | RegexOptions . IgnoreCase ) ;
2014-11-07 04:02:07 +00:00
2017-01-02 17:05:55 +00:00
public void Scan ( Movie movie )
{
2019-07-10 03:14:53 +00:00
var rootFolder = _rootFolderService . GetBestRootFolderPath ( movie . Path ) ;
2017-01-02 17:05:55 +00:00
2020-05-13 19:27:39 +00:00
var movieFolderExists = _diskProvider . FolderExists ( movie . Path ) ;
2017-01-02 17:05:55 +00:00
2020-05-13 19:27:39 +00:00
if ( ! movieFolderExists )
2017-01-02 17:05:55 +00:00
{
2020-05-13 19:27:39 +00:00
if ( ! _diskProvider . FolderExists ( rootFolder ) )
{
_logger . Warn ( "Movie's root folder ({0}) doesn't exist." , rootFolder ) ;
_eventAggregator . PublishEvent ( new MovieScanSkippedEvent ( movie , MovieScanSkippedReason . RootFolderDoesNotExist ) ) ;
return ;
}
if ( _diskProvider . FolderEmpty ( rootFolder ) )
{
_logger . Warn ( "Movie's root folder ({0}) is empty." , rootFolder ) ;
_eventAggregator . PublishEvent ( new MovieScanSkippedEvent ( movie , MovieScanSkippedReason . RootFolderIsEmpty ) ) ;
return ;
}
2017-01-02 17:05:55 +00:00
}
_logger . ProgressInfo ( "Scanning disk for {0}" , movie . Title ) ;
2020-05-13 19:27:39 +00:00
if ( ! movieFolderExists )
2017-01-02 17:05:55 +00:00
{
2019-07-10 03:14:53 +00:00
if ( _configService . CreateEmptyMovieFolders )
2017-01-02 17:05:55 +00:00
{
2020-08-02 05:47:18 +00:00
if ( _configService . DeleteEmptyFolders )
{
2021-01-26 15:32:31 +00:00
_logger . Debug ( "Not creating missing movie folder: {0} because delete empty movie folders is enabled" , movie . Path ) ;
2020-08-02 05:47:18 +00:00
}
else
{
2021-01-26 15:32:31 +00:00
_logger . Debug ( "Creating missing movie folder: {0}" , movie . Path ) ;
2020-08-02 05:47:18 +00:00
_diskProvider . CreateFolder ( movie . Path ) ;
SetPermissions ( movie . Path ) ;
}
2018-02-03 15:44:35 +00:00
}
2018-03-14 20:41:36 +00:00
else
{
2021-01-26 15:32:31 +00:00
_logger . Debug ( "Movie's folder doesn't exist: {0}" , movie . Path ) ;
2018-03-14 20:41:36 +00:00
}
2017-01-02 17:05:55 +00:00
2019-07-10 03:14:53 +00:00
CleanMediaFiles ( movie , new List < string > ( ) ) ;
CompletedScanning ( movie ) ;
2017-01-02 17:05:55 +00:00
return ;
}
var videoFilesStopwatch = Stopwatch . StartNew ( ) ;
2020-09-05 05:23:24 +00:00
var mediaFileList = FilterPaths ( movie . Path , GetVideoFiles ( movie . Path ) ) . ToList ( ) ;
2017-01-02 17:05:55 +00:00
videoFilesStopwatch . Stop ( ) ;
2018-03-14 20:41:36 +00:00
_logger . Trace ( "Finished getting movie files for: {0} [{1}]" , movie , videoFilesStopwatch . Elapsed ) ;
2017-01-02 17:05:55 +00:00
2019-07-10 03:14:53 +00:00
CleanMediaFiles ( movie , mediaFileList ) ;
2017-01-02 17:05:55 +00:00
2021-11-28 21:42:44 +00:00
var movieFiles = _mediaFileService . GetFilesByMovie ( movie . Id ) ;
var unmappedFiles = MediaFileService . FilterExistingFiles ( mediaFileList , movieFiles , movie ) ;
2017-01-02 17:05:55 +00:00
var decisionsStopwatch = Stopwatch . StartNew ( ) ;
2021-11-28 21:42:44 +00:00
var decisions = _importDecisionMaker . GetImportDecisions ( unmappedFiles , movie , false ) ;
2017-01-02 17:05:55 +00:00
decisionsStopwatch . Stop ( ) ;
_logger . Trace ( "Import decisions complete for: {0} [{1}]" , movie , decisionsStopwatch . Elapsed ) ;
2017-01-09 03:16:14 +00:00
_importApprovedMovies . Import ( decisions , false ) ;
2017-01-02 17:05:55 +00:00
2021-11-28 21:42:44 +00:00
// Update existing files that have a different file size
var fileInfoStopwatch = Stopwatch . StartNew ( ) ;
var filesToUpdate = new List < MovieFile > ( ) ;
foreach ( var file in movieFiles )
{
var path = Path . Combine ( movie . Path , file . RelativePath ) ;
var fileSize = _diskProvider . GetFileSize ( path ) ;
if ( file . Size = = fileSize )
{
continue ;
}
file . Size = fileSize ;
if ( ! _updateMediaInfoService . Update ( file , movie ) )
{
filesToUpdate . Add ( file ) ;
}
}
// Update any files that had a file size change, but didn't get media info updated.
if ( filesToUpdate . Any ( ) )
{
_mediaFileService . Update ( filesToUpdate ) ;
}
fileInfoStopwatch . Stop ( ) ;
_logger . Trace ( "Reprocessing existing files complete for: {0} [{1}]" , movie , decisionsStopwatch . Elapsed ) ;
2018-11-23 07:03:32 +00:00
RemoveEmptyMovieFolder ( movie . Path ) ;
2019-07-10 03:14:53 +00:00
CompletedScanning ( movie ) ;
}
private void CleanMediaFiles ( Movie movie , List < string > mediaFileList )
{
_logger . Debug ( "{0} Cleaning up media files in DB" , movie ) ;
_mediaFileTableCleanupService . Clean ( movie , mediaFileList ) ;
}
2018-11-23 07:03:32 +00:00
2019-07-10 03:14:53 +00:00
private void CompletedScanning ( Movie movie )
{
2017-01-02 17:05:55 +00:00
_logger . Info ( "Completed scanning disk for {0}" , movie . Title ) ;
_eventAggregator . PublishEvent ( new MovieScannedEvent ( movie ) ) ;
}
2013-05-13 00:36:23 +00:00
public string [ ] GetVideoFiles ( string path , bool allDirectories = true )
2011-06-20 01:59:31 +00:00
{
2013-08-18 04:28:34 +00:00
_logger . Debug ( "Scanning '{0}' for video files" , path ) ;
2011-06-20 01:59:31 +00:00
2012-08-27 10:15:22 +00:00
var searchOption = allDirectories ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
2019-07-10 03:14:53 +00:00
var filesOnDisk = _diskProvider . GetFiles ( path , searchOption ) . ToList ( ) ;
2011-06-20 01:59:31 +00:00
2018-02-15 12:39:01 +00:00
var mediaFileList = filesOnDisk . Where ( file = > MediaFileExtensions . Extensions . Contains ( Path . GetExtension ( file ) ) )
2015-05-12 00:23:55 +00:00
. ToList ( ) ;
2011-06-20 01:59:31 +00:00
2019-07-10 03:14:53 +00:00
_logger . Trace ( "{0} files were found in {1}" , filesOnDisk . Count , path ) ;
2013-11-08 01:52:50 +00:00
_logger . Debug ( "{0} video files were found in {1}" , mediaFileList . Count , path ) ;
2019-07-10 03:14:53 +00:00
2013-04-15 01:41:39 +00:00
return mediaFileList . ToArray ( ) ;
2011-06-20 01:59:31 +00:00
}
2013-05-13 00:36:23 +00:00
2016-09-18 18:04:56 +00:00
public string [ ] GetNonVideoFiles ( string path , bool allDirectories = true )
2015-02-21 19:49:56 +00:00
{
2016-09-18 18:04:56 +00:00
_logger . Debug ( "Scanning '{0}' for non-video files" , path ) ;
var searchOption = allDirectories ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
2019-07-10 03:14:53 +00:00
var filesOnDisk = _diskProvider . GetFiles ( path , searchOption ) . ToList ( ) ;
2016-09-18 18:04:56 +00:00
2018-02-15 12:39:01 +00:00
var mediaFileList = filesOnDisk . Where ( file = > ! MediaFileExtensions . Extensions . Contains ( Path . GetExtension ( file ) ) )
2016-09-18 18:04:56 +00:00
. ToList ( ) ;
2019-07-10 03:14:53 +00:00
_logger . Trace ( "{0} files were found in {1}" , filesOnDisk . Count , path ) ;
2016-09-18 18:04:56 +00:00
_logger . Debug ( "{0} non-video files were found in {1}" , mediaFileList . Count , path ) ;
2019-07-10 03:14:53 +00:00
2016-09-18 18:04:56 +00:00
return mediaFileList . ToArray ( ) ;
}
2020-09-05 05:23:24 +00:00
public List < string > FilterPaths ( string basePath , IEnumerable < string > paths , bool filterExtras = true )
2017-01-02 17:05:55 +00:00
{
2020-09-05 05:23:24 +00:00
var filteredPaths = paths . Where ( path = > ! ExcludedSubFoldersRegex . IsMatch ( basePath . GetRelativePath ( path ) ) )
. Where ( path = > ! ExcludedFilesRegex . IsMatch ( Path . GetFileName ( path ) ) )
. ToList ( ) ;
2020-07-02 01:48:13 +00:00
if ( filterExtras )
{
2020-09-05 05:23:24 +00:00
filteredPaths = filteredPaths . Where ( path = > ! ExcludedExtrasSubFolderRegex . IsMatch ( basePath . GetRelativePath ( path ) ) )
. Where ( path = > ! ExcludedExtraFilesRegex . IsMatch ( Path . GetFileName ( path ) ) )
2020-07-22 01:53:31 +00:00
. ToList ( ) ;
2020-07-02 01:48:13 +00:00
}
2020-09-05 05:23:24 +00:00
return filteredPaths ;
2017-01-02 17:05:55 +00:00
}
2015-10-03 17:45:26 +00:00
private void SetPermissions ( string path )
2014-08-11 04:20:06 +00:00
{
if ( ! _configService . SetPermissionsLinux )
{
return ;
}
try
{
2020-11-21 04:33:10 +00:00
_diskProvider . SetPermissions ( path , _configService . ChmodFolder , _configService . ChownGroup ) ;
2014-08-11 04:20:06 +00:00
}
catch ( Exception ex )
{
2016-02-11 21:13:42 +00:00
_logger . Warn ( ex , "Unable to apply permissions to: " + path ) ;
_logger . Debug ( ex , ex . Message ) ;
2014-08-11 04:20:06 +00:00
}
2018-02-15 12:39:01 +00:00
}
2014-08-11 04:20:06 +00:00
2018-11-23 07:03:32 +00:00
private void RemoveEmptyMovieFolder ( string path )
2017-01-02 17:05:55 +00:00
{
2019-07-10 03:14:53 +00:00
if ( _configService . DeleteEmptyFolders )
{
2020-05-16 17:08:55 +00:00
_diskProvider . RemoveEmptySubfolders ( path ) ;
if ( _diskProvider . FolderEmpty ( path ) )
2018-11-23 07:03:32 +00:00
{
_diskProvider . DeleteFolder ( path , true ) ;
}
2019-07-10 03:14:53 +00:00
}
2017-01-02 17:05:55 +00:00
}
public void Execute ( RescanMovieCommand message )
{
if ( message . MovieId . HasValue )
{
2017-02-11 19:38:17 +00:00
var movie = _movieService . GetMovie ( message . MovieId . Value ) ;
Scan ( movie ) ;
2017-01-02 17:05:55 +00:00
}
else
{
var allMovies = _movieService . GetAllMovies ( ) ;
foreach ( var movie in allMovies )
{
Scan ( movie ) ;
}
}
}
2011-06-20 01:59:31 +00:00
}
2017-03-04 16:50:02 +00:00
}