mirror of
https://github.com/Radarr/Radarr.git
synced 2024-10-03 22:57:18 +02:00
New: Custom import scripts can communicate information back
(cherry picked from commit b4ac495983d61819d9ab84f49c880957ba57418b)
This commit is contained in:
parent
dca9d69aaa
commit
d72f78d979
@ -156,7 +156,7 @@ public void should_not_scan_extras_subfolder()
|
||||
Subject.Scan(_movie);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Verify(v => v.GetFiles(It.IsAny<string>(), It.IsAny<bool>()), Times.Once());
|
||||
.Verify(v => v.GetFiles(It.IsAny<string>(), It.IsAny<bool>()), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IMakeImportDecision>()
|
||||
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _movie, false), Times.Once());
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
@ -71,7 +72,7 @@ public void should_skip_up_to_date_media_info()
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new MovieScannedEvent(_movie));
|
||||
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
|
||||
@ -97,7 +98,7 @@ public void should_skip_not_yet_date_media_info()
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new MovieScannedEvent(_movie));
|
||||
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(2));
|
||||
@ -123,7 +124,7 @@ public void should_update_outdated_media_info()
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new MovieScannedEvent(_movie));
|
||||
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(3));
|
||||
@ -146,7 +147,7 @@ public void should_ignore_missing_files()
|
||||
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new MovieScannedEvent(_movie));
|
||||
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo("media.mkv"), Times.Never());
|
||||
@ -173,7 +174,7 @@ public void should_continue_after_failure()
|
||||
GivenSuccessfulScan();
|
||||
GivenFailedScan(Path.Combine(_movie.Path, "media2.mkv"));
|
||||
|
||||
Subject.Handle(new MovieScannedEvent(_movie));
|
||||
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_movie.Path, "media.mkv")), Times.Exactly(1));
|
||||
@ -203,7 +204,7 @@ public void should_not_update_files_if_media_info_disabled()
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new MovieScannedEvent(_movie));
|
||||
Subject.Handle(new MovieScannedEvent(_movie, new List<string>()));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(It.IsAny<string>()), Times.Never());
|
||||
|
@ -2,45 +2,33 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Movies;
|
||||
|
||||
namespace NzbDrone.Core.Extras
|
||||
{
|
||||
public class ExistingExtraFileService : IHandle<MovieScannedEvent>
|
||||
public interface IExistingExtraFiles
|
||||
{
|
||||
List<string> ImportExtraFiles(Movie movie, List<string> possibleExtraFiles);
|
||||
}
|
||||
|
||||
public class ExistingExtraFileService : IExistingExtraFiles, IHandle<MovieScannedEvent>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly List<IImportExistingExtraFiles> _existingExtraFileImporters;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ExistingExtraFileService(IDiskProvider diskProvider,
|
||||
IDiskScanService diskScanService,
|
||||
IEnumerable<IImportExistingExtraFiles> existingExtraFileImporters,
|
||||
public ExistingExtraFileService(IEnumerable<IImportExistingExtraFiles> existingExtraFileImporters,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_diskScanService = diskScanService;
|
||||
_existingExtraFileImporters = existingExtraFileImporters.OrderBy(e => e.Order).ToList();
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Handle(MovieScannedEvent message)
|
||||
public List<string> ImportExtraFiles(Movie movie, List<string> possibleExtraFiles)
|
||||
{
|
||||
var movie = message.Movie;
|
||||
|
||||
if (!_diskProvider.FolderExists(movie.Path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug("Looking for existing extra files in {0}", movie.Path);
|
||||
|
||||
var filesOnDisk = _diskScanService.GetNonVideoFiles(movie.Path);
|
||||
var possibleExtraFiles = _diskScanService.FilterPaths(movie.Path, filesOnDisk, false);
|
||||
|
||||
var importedFiles = new List<string>();
|
||||
|
||||
foreach (var existingExtraFileImporter in _existingExtraFileImporters)
|
||||
@ -50,6 +38,15 @@ public void Handle(MovieScannedEvent message)
|
||||
importedFiles.AddRange(imported.Select(f => Path.Combine(movie.Path, f.RelativePath)));
|
||||
}
|
||||
|
||||
return importedFiles;
|
||||
}
|
||||
|
||||
public void Handle(MovieScannedEvent message)
|
||||
{
|
||||
var movie = message.Movie;
|
||||
var possibleExtraFiles = message.PossibleExtraFiles;
|
||||
var importedFiles = ImportExtraFiles(movie, possibleExtraFiles);
|
||||
|
||||
_logger.Info("Found {0} possible extra files, imported {1} files.", possibleExtraFiles.Count, importedFiles.Count);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ namespace NzbDrone.Core.Extras
|
||||
{
|
||||
public interface IExtraService
|
||||
{
|
||||
void MoveFilesAfterRename(Movie movie, MovieFile movieFile);
|
||||
void ImportMovie(LocalMovie localMovie, MovieFile movieFile, bool isReadOnly);
|
||||
}
|
||||
|
||||
@ -139,6 +140,16 @@ public void Handle(MovieFolderCreatedEvent message)
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveFilesAfterRename(Movie movie, MovieFile movieFile)
|
||||
{
|
||||
var movieFiles = new List<MovieFile> { movieFile };
|
||||
|
||||
foreach (var extraFileManager in _extraFileManagers)
|
||||
{
|
||||
extraFileManager.MoveFilesAfterRename(movie, movieFiles);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(MovieRenamedEvent message)
|
||||
{
|
||||
var movie = message.Movie;
|
||||
|
@ -121,7 +121,7 @@ public void Scan(Movie movie)
|
||||
}
|
||||
|
||||
CleanMediaFiles(movie, new List<string>());
|
||||
CompletedScanning(movie);
|
||||
CompletedScanning(movie, new List<string>());
|
||||
|
||||
return;
|
||||
}
|
||||
@ -173,8 +173,11 @@ public void Scan(Movie movie)
|
||||
fileInfoStopwatch.Stop();
|
||||
_logger.Trace("Reprocessing existing files complete for: {0} [{1}]", movie, decisionsStopwatch.Elapsed);
|
||||
|
||||
var filesOnDisk = GetNonVideoFiles(movie.Path);
|
||||
var possibleExtraFiles = FilterPaths(movie.Path, filesOnDisk);
|
||||
|
||||
RemoveEmptyMovieFolder(movie.Path);
|
||||
CompletedScanning(movie);
|
||||
CompletedScanning(movie, possibleExtraFiles);
|
||||
}
|
||||
|
||||
private void CleanMediaFiles(Movie movie, List<string> mediaFileList)
|
||||
@ -183,10 +186,10 @@ private void CleanMediaFiles(Movie movie, List<string> mediaFileList)
|
||||
_mediaFileTableCleanupService.Clean(movie, mediaFileList);
|
||||
}
|
||||
|
||||
private void CompletedScanning(Movie movie)
|
||||
private void CompletedScanning(Movie movie, List<string> possibleExtraFiles)
|
||||
{
|
||||
_logger.Info("Completed scanning disk for {0}", movie.Title);
|
||||
_eventAggregator.PublishEvent(new MovieScannedEvent(movie));
|
||||
_eventAggregator.PublishEvent(new MovieScannedEvent(movie, possibleExtraFiles));
|
||||
}
|
||||
|
||||
public string[] GetVideoFiles(string path, bool allDirectories = true)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using NzbDrone.Common.Messaging;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Movies;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Events
|
||||
@ -6,10 +7,12 @@ namespace NzbDrone.Core.MediaFiles.Events
|
||||
public class MovieScannedEvent : IEvent
|
||||
{
|
||||
public Movie Movie { get; private set; }
|
||||
public List<string> PossibleExtraFiles { get; set; }
|
||||
|
||||
public MovieScannedEvent(Movie movie)
|
||||
public MovieScannedEvent(Movie movie, List<string> possibleExtraFiles)
|
||||
{
|
||||
Movie = movie;
|
||||
PossibleExtraFiles = possibleExtraFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ private MovieFile TransferFile(MovieFile movieFile, Movie movie, string destinat
|
||||
try
|
||||
{
|
||||
MoveMovieFile(movieFile, movie);
|
||||
localMovie.FileRenamedAfterScriptImport = true;
|
||||
}
|
||||
catch (SameFilenameException)
|
||||
{
|
||||
|
@ -27,6 +27,7 @@ public class ImportApprovedMovie : IImportApprovedMovie
|
||||
private readonly IUpgradeMediaFiles _movieFileUpgrader;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IExtraService _extraService;
|
||||
private readonly IExistingExtraFiles _existingExtraFiles;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
@ -36,6 +37,7 @@ public class ImportApprovedMovie : IImportApprovedMovie
|
||||
public ImportApprovedMovie(IUpgradeMediaFiles movieFileUpgrader,
|
||||
IMediaFileService mediaFileService,
|
||||
IExtraService extraService,
|
||||
IExistingExtraFiles existingExtraFiles,
|
||||
IDiskProvider diskProvider,
|
||||
IHistoryService historyService,
|
||||
IEventAggregator eventAggregator,
|
||||
@ -45,6 +47,7 @@ public ImportApprovedMovie(IUpgradeMediaFiles movieFileUpgrader,
|
||||
_movieFileUpgrader = movieFileUpgrader;
|
||||
_mediaFileService = mediaFileService;
|
||||
_extraService = extraService;
|
||||
_existingExtraFiles = existingExtraFiles;
|
||||
_diskProvider = diskProvider;
|
||||
_historyService = historyService;
|
||||
_eventAggregator = eventAggregator;
|
||||
@ -146,7 +149,20 @@ public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownloa
|
||||
|
||||
if (newDownload)
|
||||
{
|
||||
_extraService.ImportMovie(localMovie, movieFile, copyOnly);
|
||||
if (localMovie.ScriptImported)
|
||||
{
|
||||
_existingExtraFiles.ImportExtraFiles(localMovie.Movie, localMovie.PossibleExtraFiles);
|
||||
|
||||
if (localMovie.FileRenamedAfterScriptImport)
|
||||
{
|
||||
_extraService.MoveFilesAfterRename(localMovie.Movie, movieFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (!localMovie.ScriptImported || localMovie.ShouldImportExtras)
|
||||
{
|
||||
_extraService.ImportMovie(localMovie, movieFile, copyOnly);
|
||||
}
|
||||
}
|
||||
|
||||
_eventAggregator.PublishEvent(new MovieFileImportedEvent(localMovie, movieFile, oldFiles, newDownload, downloadClientItem));
|
||||
|
@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
@ -25,6 +27,7 @@ public class ImportScriptService : IImportScript
|
||||
private readonly IProcessProvider _processProvider;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ITagRepository _tagRepository;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ImportScriptService(IProcessProvider processProvider,
|
||||
@ -32,6 +35,7 @@ public ImportScriptService(IProcessProvider processProvider,
|
||||
IConfigService configService,
|
||||
IConfigFileProvider configFileProvider,
|
||||
ITagRepository tagRepository,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_processProvider = processProvider;
|
||||
@ -39,9 +43,73 @@ public ImportScriptService(IProcessProvider processProvider,
|
||||
_configService = configService;
|
||||
_configFileProvider = configFileProvider;
|
||||
_tagRepository = tagRepository;
|
||||
_diskProvider = diskProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private static readonly Regex OutputRegex = new Regex(@"^(?:\[(?:(?<mediaFile>MediaFile)|(?<extraFile>ExtraFile))\]\s?(?<fileName>.+)|(?<preventExtraImport>\[PreventExtraImport\])|\[MoveStatus\]\s?(?:(?<deferMove>DeferMove)|(?<moveComplete>MoveComplete)|(?<renameRequested>RenameRequested)))$", RegexOptions.Compiled);
|
||||
|
||||
private ScriptImportInfo ProcessOutput(List<ProcessOutputLine> processOutputLines)
|
||||
{
|
||||
var possibleExtraFiles = new List<string>();
|
||||
string mediaFile = null;
|
||||
var decision = ScriptImportDecision.MoveComplete;
|
||||
var importExtraFiles = true;
|
||||
|
||||
foreach (var line in processOutputLines)
|
||||
{
|
||||
var match = OutputRegex.Match(line.Content);
|
||||
|
||||
if (match.Groups["mediaFile"].Success)
|
||||
{
|
||||
if (mediaFile is not null)
|
||||
{
|
||||
throw new ScriptImportException("Script output contains multiple media files. Only one media file can be returned.");
|
||||
}
|
||||
|
||||
mediaFile = match.Groups["fileName"].Value;
|
||||
|
||||
if (!MediaFileExtensions.Extensions.Contains(Path.GetExtension(mediaFile)))
|
||||
{
|
||||
throw new ScriptImportException("Script output contains invalid media file: {0}", mediaFile);
|
||||
}
|
||||
else if (!_diskProvider.FileExists(mediaFile))
|
||||
{
|
||||
throw new ScriptImportException("Script output contains non-existent media file: {0}", mediaFile);
|
||||
}
|
||||
}
|
||||
else if (match.Groups["extraFile"].Success)
|
||||
{
|
||||
var fileName = match.Groups["fileName"].Value;
|
||||
|
||||
if (!_diskProvider.FileExists(fileName))
|
||||
{
|
||||
_logger.Warn("Script output contains non-existent possible extra file: {0}", fileName);
|
||||
}
|
||||
|
||||
possibleExtraFiles.Add(fileName);
|
||||
}
|
||||
else if (match.Groups["moveComplete"].Success)
|
||||
{
|
||||
decision = ScriptImportDecision.MoveComplete;
|
||||
}
|
||||
else if (match.Groups["renameRequested"].Success)
|
||||
{
|
||||
decision = ScriptImportDecision.RenameRequested;
|
||||
}
|
||||
else if (match.Groups["deferMove"].Success)
|
||||
{
|
||||
decision = ScriptImportDecision.DeferMove;
|
||||
}
|
||||
else if (match.Groups["preventExtraImport"].Success)
|
||||
{
|
||||
importExtraFiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
return new ScriptImportInfo(possibleExtraFiles, mediaFile, decision, importExtraFiles);
|
||||
}
|
||||
|
||||
public ScriptImportDecision TryImport(string sourcePath, string destinationFilePath, LocalMovie localMovie, MovieFile movieFile, TransferMode mode)
|
||||
{
|
||||
var movie = localMovie.Movie;
|
||||
@ -111,22 +179,37 @@ public ScriptImportDecision TryImport(string sourcePath, string destinationFileP
|
||||
|
||||
var processOutput = _processProvider.StartAndCapture(_configService.ScriptImportPath, $"\"{sourcePath}\" \"{destinationFilePath}\"", environmentVariables);
|
||||
|
||||
_logger.Debug("Executed external script: {0} - Status: {1}", _configService.ScriptImportPath, processOutput.ExitCode);
|
||||
_logger.Debug("Script Output: \r\n{0}", string.Join("\r\n", processOutput.Lines));
|
||||
|
||||
switch (processOutput.ExitCode)
|
||||
if (processOutput.ExitCode != 0)
|
||||
{
|
||||
case 0: // Copy complete
|
||||
return ScriptImportDecision.MoveComplete;
|
||||
case 2: // Copy complete, file potentially changed, should try renaming again
|
||||
movieFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(destinationFilePath);
|
||||
movieFile.Path = null;
|
||||
return ScriptImportDecision.RenameRequested;
|
||||
case 3: // Let Radarr handle it
|
||||
return ScriptImportDecision.DeferMove;
|
||||
default: // Error, fail to import
|
||||
throw new ScriptImportException("Moving with script failed! Exit code {0}", processOutput.ExitCode);
|
||||
throw new ScriptImportException("Script exited with non-zero exit code: {0}", processOutput.ExitCode);
|
||||
}
|
||||
|
||||
var scriptImportInfo = ProcessOutput(processOutput.Lines);
|
||||
|
||||
var mediaFile = scriptImportInfo.MediaFile ?? destinationFilePath;
|
||||
localMovie.PossibleExtraFiles = scriptImportInfo.PossibleExtraFiles;
|
||||
|
||||
movieFile.RelativePath = movie.Path.GetRelativePath(mediaFile);
|
||||
movieFile.Path = mediaFile;
|
||||
|
||||
var exitCode = processOutput.ExitCode;
|
||||
|
||||
localMovie.ShouldImportExtras = scriptImportInfo.ImportExtraFiles;
|
||||
|
||||
if (scriptImportInfo.Decision != ScriptImportDecision.DeferMove)
|
||||
{
|
||||
localMovie.ScriptImported = true;
|
||||
}
|
||||
|
||||
if (scriptImportInfo.Decision == ScriptImportDecision.RenameRequested)
|
||||
{
|
||||
movieFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(mediaFile);
|
||||
movieFile.Path = null;
|
||||
}
|
||||
|
||||
return scriptImportInfo.Decision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
src/NzbDrone.Core/MediaFiles/ScriptImportInfo.cs
Normal file
20
src/NzbDrone.Core/MediaFiles/ScriptImportInfo.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public struct ScriptImportInfo
|
||||
{
|
||||
public List<string> PossibleExtraFiles { get; set; }
|
||||
public string MediaFile { get; set; }
|
||||
public ScriptImportDecision Decision { get; set; }
|
||||
public bool ImportExtraFiles { get; set; }
|
||||
|
||||
public ScriptImportInfo(List<string> possibleExtraFiles, string mediaFile, ScriptImportDecision decision, bool importExtraFiles)
|
||||
{
|
||||
PossibleExtraFiles = possibleExtraFiles;
|
||||
MediaFile = mediaFile;
|
||||
Decision = decision;
|
||||
ImportExtraFiles = importExtraFiles;
|
||||
}
|
||||
}
|
||||
}
|
@ -36,6 +36,10 @@ public LocalMovie()
|
||||
public List<CustomFormat> CustomFormats { get; set; }
|
||||
public int CustomFormatScore { get; set; }
|
||||
public GrabbedReleaseInfo Release { get; set; }
|
||||
public bool ScriptImported { get; set; }
|
||||
public bool FileRenamedAfterScriptImport { get; set; }
|
||||
public bool ShouldImportExtras { get; set; }
|
||||
public List<string> PossibleExtraFiles { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user