From 9f1e2151206a077334a9c34a12a373b465752d87 Mon Sep 17 00:00:00 2001 From: JeWe37 Date: Tue, 23 May 2023 05:36:17 +0200 Subject: [PATCH] New: Option to Import via Script Closes #791 --- .../MediaManagement/MediaManagement.js | 63 +++++++-- .../Configuration/ConfigService.cs | 14 ++ .../Configuration/IConfigService.cs | 2 + .../MediaFiles/EpisodeFileMovingService.cs | 41 +++++- .../EpisodeImport/ImportApprovedEpisodes.cs | 4 +- .../EpisodeImport/ImportDecisionMaker.cs | 1 + .../Manual/ManualImportService.cs | 3 + .../MediaInfo/UpdateMediaInfoService.cs | 3 +- .../MediaFiles/ScriptImportDecider.cs | 129 ++++++++++++++++++ .../MediaFiles/ScriptImportDecision.cs | 10 ++ .../MediaFiles/ScriptImportException.cs | 23 ++++ .../MediaFiles/UpgradeMediaFileService.cs | 3 + .../Parser/Model/LocalEpisode.cs | 4 + .../Config/MediaManagementConfigController.cs | 2 + .../Config/MediaManagementConfigResource.cs | 4 + src/Sonarr.Api.V3/openapi.json | 6 + 16 files changed, 290 insertions(+), 22 deletions(-) create mode 100644 src/NzbDrone.Core/MediaFiles/ScriptImportDecider.cs create mode 100644 src/NzbDrone.Core/MediaFiles/ScriptImportDecision.cs create mode 100644 src/NzbDrone.Core/MediaFiles/ScriptImportException.cs diff --git a/frontend/src/Settings/MediaManagement/MediaManagement.js b/frontend/src/Settings/MediaManagement/MediaManagement.js index 96606c094..a5f9a0aa3 100644 --- a/frontend/src/Settings/MediaManagement/MediaManagement.js +++ b/frontend/src/Settings/MediaManagement/MediaManagement.js @@ -68,27 +68,27 @@ class MediaManagement extends Component { { - isFetching && + isFetching ?
-
+ : null } { - !isFetching && error && + !isFetching && error ?
Unable to load Media Management settings
-
+ : null } { - hasSettings && !isFetching && !error && + hasSettings && !isFetching && !error ?
{ - advancedSettings && + advancedSettings ?
-
+ : null } { - advancedSettings && + advancedSettings ?
@@ -200,6 +200,41 @@ class MediaManagement extends Component { /> + + Import Using Script + + + + + { + settings.useScriptImport.value ? + + Import Script Path + + + : null + } + Import Extra Files @@ -213,7 +248,7 @@ class MediaManagement extends Component { { - settings.importExtraFiles.value && + settings.importExtraFiles.value ? - + : null } -
+ : null }
{ - advancedSettings && !isWindows && + advancedSettings && !isWindows ?
@@ -411,9 +446,9 @@ class MediaManagement extends Component { {...settings.chownGroup} /> -
+
: null } -
+ : null }
diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 02d7d3f32..12c8bd82c 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -207,6 +207,20 @@ namespace NzbDrone.Core.Configuration set { SetValue("EnableMediaInfo", value); } } + public bool UseScriptImport + { + get { return GetValueBoolean("UseScriptImport", false); } + + set { SetValue("UseScriptImport", value); } + } + + public string ScriptImportPath + { + get { return GetValue("ScriptImportPath"); } + + set { SetValue("ScriptImportPath", value); } + } + public bool ImportExtraFiles { get { return GetValueBoolean("ImportExtraFiles", false); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index c2fa55aa2..2ebc262ac 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -33,6 +33,8 @@ namespace NzbDrone.Core.Configuration int MinimumFreeSpaceWhenImporting { get; set; } bool CopyUsingHardlinks { get; set; } bool EnableMediaInfo { get; set; } + bool UseScriptImport { get; set; } + string ScriptImportPath { get; set; } bool ImportExtraFiles { get; set; } string ExtraFileExtensions { get; set; } RescanAfterRefreshType RescanAfterRefresh { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index 805f9f1fb..161437723 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -31,6 +31,7 @@ namespace NzbDrone.Core.MediaFiles private readonly IDiskTransferService _diskTransferService; private readonly IDiskProvider _diskProvider; private readonly IMediaFileAttributeService _mediaFileAttributeService; + private readonly IImportScript _scriptImportDecider; private readonly IEventAggregator _eventAggregator; private readonly IConfigService _configService; private readonly Logger _logger; @@ -41,6 +42,7 @@ namespace NzbDrone.Core.MediaFiles IDiskTransferService diskTransferService, IDiskProvider diskProvider, IMediaFileAttributeService mediaFileAttributeService, + IImportScript scriptImportDecider, IEventAggregator eventAggregator, IConfigService configService, Logger logger) @@ -51,6 +53,7 @@ namespace NzbDrone.Core.MediaFiles _diskTransferService = diskTransferService; _diskProvider = diskProvider; _mediaFileAttributeService = mediaFileAttributeService; + _scriptImportDecider = scriptImportDecider; _eventAggregator = eventAggregator; _configService = configService; _logger = logger; @@ -59,6 +62,11 @@ namespace NzbDrone.Core.MediaFiles public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series) { var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id); + return MoveEpisodeFile(episodeFile, series, episodes); + } + + private EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series, List episodes) + { var filePath = _buildFileNames.BuildFilePath(episodes, series, episodeFile, Path.GetExtension(episodeFile.RelativePath)); EnsureEpisodeFolder(episodeFile, series, episodes.Select(v => v.SeasonNumber).First(), filePath); @@ -76,7 +84,7 @@ namespace NzbDrone.Core.MediaFiles _logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath); - return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move); + return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move, localEpisode); } public EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode) @@ -88,14 +96,14 @@ namespace NzbDrone.Core.MediaFiles if (_configService.CopyUsingHardlinks) { _logger.Debug("Hardlinking episode file: {0} to {1}", episodeFile.Path, filePath); - return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy); + return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy, localEpisode); } _logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath); - return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy); + return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy, localEpisode); } - private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List episodes, string destinationFilePath, TransferMode mode) + private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List episodes, string destinationFilePath, TransferMode mode, LocalEpisode localEpisode = null) { Ensure.That(episodeFile, () => episodeFile).IsNotNull(); Ensure.That(series, () => series).IsNotNull(); @@ -113,10 +121,33 @@ namespace NzbDrone.Core.MediaFiles throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath); } - _diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode); + var transfer = true; episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath); + if (localEpisode is not null) + { + var scriptImportDecision = _scriptImportDecider.TryImport(episodeFilePath, destinationFilePath, localEpisode, episodeFile, mode); + + switch (scriptImportDecision) + { + case ScriptImportDecision.DeferMove: + break; + case ScriptImportDecision.RenameRequested: + MoveEpisodeFile(episodeFile, series, episodeFile.Episodes); + transfer = false; + break; + case ScriptImportDecision.MoveComplete: + transfer = false; + break; + } + } + + if (transfer) + { + _diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode); + } + _updateEpisodeFileService.ChangeFileDateForFile(episodeFile, series, episodes); try diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 5e9b81b64..71a714c71 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.Download; using NzbDrone.Core.Extras; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser.Model; @@ -110,8 +111,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport episodeFile.SceneName = localEpisode.SceneName; episodeFile.OriginalFilePath = GetOriginalFilePath(downloadClientItem, localEpisode); - var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly); - oldFiles = moveResult.OldFiles; + oldFiles = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly).OldFiles; } else { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index 5c7d3b55a..d5164f1b1 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -89,6 +89,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { Series = series, DownloadClientEpisodeInfo = downloadClientItemInfo, + DownloadItem = downloadClientItem, FolderEpisodeInfo = folderInfo, Path = file, SceneSource = sceneSource, diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index 336b291c1..33be2c397 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -161,6 +161,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual localEpisode.Episodes = _episodeService.GetEpisodes(episodeIds); localEpisode.FileEpisodeInfo = Parser.Parser.ParsePath(path); localEpisode.DownloadClientEpisodeInfo = downloadClientItem == null ? null : Parser.Parser.ParseTitle(downloadClientItem.Title); + localEpisode.DownloadItem = downloadClientItem; localEpisode.Path = path; localEpisode.SceneSource = SceneSource(series, rootFolder); localEpisode.ExistingFile = series.Path.IsParentPath(path); @@ -187,6 +188,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual DownloadClientEpisodeInfo = downloadClientItem == null ? null : Parser.Parser.ParseTitle(downloadClientItem.Title), + DownloadItem = downloadClientItem, Path = path, SceneSource = SceneSource(series, rootFolder), ExistingFile = series.Path.IsParentPath(path), @@ -479,6 +481,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual { trackedDownload = _trackedDownloadService.Find(file.DownloadId); localEpisode.DownloadClientEpisodeInfo = trackedDownload?.RemoteEpisode?.ParsedEpisodeInfo; + localEpisode.DownloadItem = trackedDownload?.DownloadItem; } if (file.FolderName.IsNotNullOrWhiteSpace()) diff --git a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs index b21a809ca..ac4371c92 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaInfo/UpdateMediaInfoService.cs @@ -12,6 +12,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo public interface IUpdateMediaInfo { bool Update(EpisodeFile episodeFile, Series series); + bool UpdateMediaInfo(EpisodeFile episodeFile, Series series); } public class UpdateMediaInfoService : IUpdateMediaInfo, IHandle @@ -65,7 +66,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo return UpdateMediaInfo(episodeFile, series); } - private bool UpdateMediaInfo(EpisodeFile episodeFile, Series series) + public bool UpdateMediaInfo(EpisodeFile episodeFile, Series series) { var path = Path.Combine(series.Path, episodeFile.RelativePath); diff --git a/src/NzbDrone.Core/MediaFiles/ScriptImportDecider.cs b/src/NzbDrone.Core/MediaFiles/ScriptImportDecider.cs new file mode 100644 index 000000000..573ef0b56 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/ScriptImportDecider.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Processes; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles +{ + public interface IImportScript + { + public ScriptImportDecision TryImport(string sourcePath, string destinationFilePath, LocalEpisode localEpisode, EpisodeFile episodeFile, TransferMode mode); + } + + public class ImportScriptService : IImportScript + { + private readonly IConfigFileProvider _configFileProvider; + private readonly IVideoFileInfoReader _videoFileInfoReader; + private readonly IProcessProvider _processProvider; + private readonly IConfigService _configService; + private readonly Logger _logger; + + public ImportScriptService(IProcessProvider processProvider, + IVideoFileInfoReader videoFileInfoReader, + IConfigService configService, + IConfigFileProvider configFileProvider, + Logger logger) + { + _processProvider = processProvider; + _videoFileInfoReader = videoFileInfoReader; + _configService = configService; + _configFileProvider = configFileProvider; + _logger = logger; + } + + public ScriptImportDecision TryImport(string sourcePath, string destinationFilePath, LocalEpisode localEpisode, EpisodeFile episodeFile, TransferMode mode) + { + var series = localEpisode.Series; + var oldFiles = localEpisode.OldFiles; + var downloadClientInfo = localEpisode.DownloadItem?.DownloadClientInfo; + var downloadId = localEpisode.DownloadItem?.DownloadId; + + if (!_configService.UseScriptImport) + { + return ScriptImportDecision.DeferMove; + } + + var environmentVariables = new StringDictionary(); + + environmentVariables.Add("Sonarr_SourcePath", sourcePath); + environmentVariables.Add("Sonarr_DestinationPath", destinationFilePath); + + environmentVariables.Add("Sonarr_InstanceName", _configFileProvider.InstanceName); + environmentVariables.Add("Sonarr_ApplicationUrl", _configService.ApplicationUrl); + environmentVariables.Add("Sonarr_TransferMode", mode.ToString()); + + environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString()); + environmentVariables.Add("Sonarr_Series_Title", series.Title); + environmentVariables.Add("Sonarr_Series_TitleSlug", series.TitleSlug); + environmentVariables.Add("Sonarr_Series_Path", series.Path); + environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString()); + environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString()); + environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId ?? string.Empty); + environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString()); + + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeCount", localEpisode.Episodes.Count.ToString()); + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeIds", string.Join(",", localEpisode.Episodes.Select(e => e.Id))); + environmentVariables.Add("Sonarr_EpisodeFile_SeasonNumber", localEpisode.SeasonNumber.ToString()); + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeNumbers", string.Join(",", localEpisode.Episodes.Select(e => e.EpisodeNumber))); + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDates", string.Join(",", localEpisode.Episodes.Select(e => e.AirDate))); + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDatesUtc", string.Join(",", localEpisode.Episodes.Select(e => e.AirDateUtc))); + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeTitles", string.Join("|", localEpisode.Episodes.Select(e => e.Title))); + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeOverviews", string.Join("|", localEpisode.Episodes.Select(e => e.Overview))); + environmentVariables.Add("Sonarr_EpisodeFile_Quality", localEpisode.Quality.Quality.Name); + environmentVariables.Add("Sonarr_EpisodeFile_QualityVersion", localEpisode.Quality.Revision.Version.ToString()); + environmentVariables.Add("Sonarr_EpisodeFile_ReleaseGroup", localEpisode.ReleaseGroup ?? string.Empty); + environmentVariables.Add("Sonarr_EpisodeFile_SceneName", localEpisode.SceneName ?? string.Empty); + + environmentVariables.Add("Sonarr_Download_Client", downloadClientInfo?.Name ?? string.Empty); + environmentVariables.Add("Sonarr_Download_Client_Type", downloadClientInfo?.Type ?? string.Empty); + environmentVariables.Add("Sonarr_Download_Id", downloadId ?? string.Empty); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_AudioChannels", MediaInfoFormatter.FormatAudioChannels(localEpisode.MediaInfo).ToString()); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_AudioCodec", MediaInfoFormatter.FormatAudioCodec(localEpisode.MediaInfo, null)); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_AudioLanguages", localEpisode.MediaInfo.AudioLanguages.Distinct().ConcatToString(" / ")); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_Languages", localEpisode.MediaInfo.AudioLanguages.ConcatToString(" / ")); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_Height", localEpisode.MediaInfo.Height.ToString()); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_Width", localEpisode.MediaInfo.Width.ToString()); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_Subtitles", localEpisode.MediaInfo.Subtitles.ConcatToString(" / ")); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_VideoCodec", MediaInfoFormatter.FormatVideoCodec(localEpisode.MediaInfo, null)); + environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_VideoDynamicRangeType", MediaInfoFormatter.FormatVideoDynamicRangeType(localEpisode.MediaInfo)); + + environmentVariables.Add("Sonarr_EpisodeFile_CustomFormat", string.Join("|", localEpisode.CustomFormats)); + environmentVariables.Add("Sonarr_EpisodeFile_CustomFormatScore", localEpisode.CustomFormatScore.ToString()); + + if (oldFiles.Any()) + { + environmentVariables.Add("Sonarr_DeletedRelativePaths", string.Join("|", oldFiles.Select(e => e.RelativePath))); + environmentVariables.Add("Sonarr_DeletedPaths", string.Join("|", oldFiles.Select(e => Path.Combine(series.Path, e.RelativePath)))); + environmentVariables.Add("Sonarr_DeletedDateAdded", string.Join("|", oldFiles.Select(e => e.DateAdded))); + } + + _logger.Debug("Executing external script: {0}", _configService.ScriptImportPath); + + 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) + { + case 0: // Copy complete + return ScriptImportDecision.MoveComplete; + case 2: // Copy complete, file potentially changed, should try renaming again + episodeFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(destinationFilePath); + episodeFile.Path = null; + return ScriptImportDecision.RenameRequested; + case 3: // Let Sonarr handle it + return ScriptImportDecision.DeferMove; + default: // Error, fail to import + throw new ScriptImportException("Moving with script failed! Exit code {0}", processOutput.ExitCode); + } + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/ScriptImportDecision.cs b/src/NzbDrone.Core/MediaFiles/ScriptImportDecision.cs new file mode 100644 index 000000000..fb2eb4f6f --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/ScriptImportDecision.cs @@ -0,0 +1,10 @@ +namespace NzbDrone.Core.MediaFiles +{ + public enum ScriptImportDecision + { + MoveComplete, + RenameRequested, + RejectExtra, + DeferMove + } +} diff --git a/src/NzbDrone.Core/MediaFiles/ScriptImportException.cs b/src/NzbDrone.Core/MediaFiles/ScriptImportException.cs new file mode 100644 index 000000000..9ac0f49d4 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/ScriptImportException.cs @@ -0,0 +1,23 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.MediaFiles +{ + public class ScriptImportException : NzbDroneException + { + public ScriptImportException(string message) + : base(message) + { + } + + public ScriptImportException(string message, params object[] args) + : base(message, args) + { + } + + public ScriptImportException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index 9844f6c07..f980e1b15 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -4,6 +4,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.MediaFiles @@ -68,6 +69,8 @@ namespace NzbDrone.Core.MediaFiles _mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade); } + localEpisode.OldFiles = moveFileResult.OldFiles; + if (copyOnly) { moveFileResult.EpisodeFile = _episodeFileMover.CopyEpisodeFile(episodeFile, localEpisode); diff --git a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs index 77169efaa..bcb9a5132 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalEpisode.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.Download; using NzbDrone.Core.Languages; +using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; @@ -22,9 +24,11 @@ namespace NzbDrone.Core.Parser.Model public long Size { get; set; } public ParsedEpisodeInfo FileEpisodeInfo { get; set; } public ParsedEpisodeInfo DownloadClientEpisodeInfo { get; set; } + public DownloadClientItem DownloadItem { get; set; } public ParsedEpisodeInfo FolderEpisodeInfo { get; set; } public Series Series { get; set; } public List Episodes { get; set; } + public List OldFiles { get; set; } public QualityModel Quality { get; set; } public List Languages { get; set; } public MediaInfoModel MediaInfo { get; set; } diff --git a/src/Sonarr.Api.V3/Config/MediaManagementConfigController.cs b/src/Sonarr.Api.V3/Config/MediaManagementConfigController.cs index 91e2b17bd..d3a649cc7 100644 --- a/src/Sonarr.Api.V3/Config/MediaManagementConfigController.cs +++ b/src/Sonarr.Api.V3/Config/MediaManagementConfigController.cs @@ -34,6 +34,8 @@ namespace Sonarr.Api.V3.Config .SetValidator(seriesPathValidator) .When(c => !string.IsNullOrWhiteSpace(c.RecycleBin)); + SharedValidator.RuleFor(c => c.ScriptImportPath).IsValidPath().When(c => c.UseScriptImport); + SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100); } diff --git a/src/Sonarr.Api.V3/Config/MediaManagementConfigResource.cs b/src/Sonarr.Api.V3/Config/MediaManagementConfigResource.cs index 16b4586a1..ee753a658 100644 --- a/src/Sonarr.Api.V3/Config/MediaManagementConfigResource.cs +++ b/src/Sonarr.Api.V3/Config/MediaManagementConfigResource.cs @@ -25,6 +25,8 @@ namespace Sonarr.Api.V3.Config public bool SkipFreeSpaceCheckWhenImporting { get; set; } public int MinimumFreeSpaceWhenImporting { get; set; } public bool CopyUsingHardlinks { get; set; } + public bool UseScriptImport { get; set; } + public string ScriptImportPath { get; set; } public bool ImportExtraFiles { get; set; } public string ExtraFileExtensions { get; set; } public bool EnableMediaInfo { get; set; } @@ -53,6 +55,8 @@ namespace Sonarr.Api.V3.Config SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting, MinimumFreeSpaceWhenImporting = model.MinimumFreeSpaceWhenImporting, CopyUsingHardlinks = model.CopyUsingHardlinks, + UseScriptImport = model.UseScriptImport, + ScriptImportPath = model.ScriptImportPath, ImportExtraFiles = model.ImportExtraFiles, ExtraFileExtensions = model.ExtraFileExtensions, EnableMediaInfo = model.EnableMediaInfo diff --git a/src/Sonarr.Api.V3/openapi.json b/src/Sonarr.Api.V3/openapi.json index ce67e79f6..92dbc85dd 100644 --- a/src/Sonarr.Api.V3/openapi.json +++ b/src/Sonarr.Api.V3/openapi.json @@ -9177,6 +9177,12 @@ "copyUsingHardlinks": { "type": "boolean" }, + "useScriptImport": { + "type": "boolean" + }, + "scriptImportPath": { + "type": "string" + }, "importExtraFiles": { "type": "boolean" },