Custom scripts

New: Run custom scripts (Connection)

Closes #439
pull/3113/head
Mark McDowall 10 years ago
parent 492b114510
commit 0f2bba0615

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Api.Notifications namespace NzbDrone.Api.Notifications
{ {
@ -9,6 +8,11 @@ namespace NzbDrone.Api.Notifications
public bool OnGrab { get; set; } public bool OnGrab { get; set; }
public bool OnDownload { get; set; } public bool OnDownload { get; set; }
public bool OnUpgrade { get; set; } public bool OnUpgrade { get; set; }
public bool OnRename { get; set; }
public bool SupportsOnGrab { get; set; }
public bool SupportsOnDownload { get; set; }
public bool SupportsOnUpgrade { get; set; }
public bool SupportsOnRename { get; set; }
public string TestCommand { get; set; } public string TestCommand { get; set; }
public HashSet<int> Tags { get; set; } public HashSet<int> Tags { get; set; }
} }

@ -177,6 +177,7 @@
<Compile Include="Extensions\PathExtensions.cs" /> <Compile Include="Extensions\PathExtensions.cs" />
<Compile Include="Processes\PidFileProvider.cs" /> <Compile Include="Processes\PidFileProvider.cs" />
<Compile Include="Processes\ProcessOutput.cs" /> <Compile Include="Processes\ProcessOutput.cs" />
<Compile Include="Processes\ProcessOutputLine.cs" />
<Compile Include="Processes\ProcessProvider.cs" /> <Compile Include="Processes\ProcessProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\SharedAssemblyInfo.cs" /> <Compile Include="Properties\SharedAssemblyInfo.cs" />

@ -1,17 +1,32 @@
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Linq;
namespace NzbDrone.Common.Processes namespace NzbDrone.Common.Processes
{ {
public class ProcessOutput public class ProcessOutput
{ {
public List<String> Standard { get; private set; } public int ExitCode { get; set; }
public List<String> Error { get; private set; } public List<ProcessOutputLine> Lines { get; set; }
public ProcessOutput() public ProcessOutput()
{ {
Standard = new List<string>(); Lines = new List<ProcessOutputLine>();
Error = new List<string>(); }
public List<ProcessOutputLine> Standard
{
get
{
return Lines.Where(c => c.Level == ProcessOutputLevel.Standard).ToList();
}
}
public List<ProcessOutputLine> Error
{
get
{
return Lines.Where(c => c.Level == ProcessOutputLevel.Error).ToList();
}
} }
} }
} }

@ -0,0 +1,29 @@
using System;
namespace NzbDrone.Common.Processes
{
public class ProcessOutputLine
{
public ProcessOutputLevel Level { get; set; }
public string Content { get; set; }
public DateTime Time { get; set; }
public ProcessOutputLine(ProcessOutputLevel level, string content)
{
Level = level;
Content = content;
Time = DateTime.UtcNow;
}
public override string ToString()
{
return String.Format("{0} - {1} - {2}", Time, Level, Content);
}
}
public enum ProcessOutputLevel
{
Standard = 0,
Error = 1
}
}

@ -1,5 +1,7 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@ -24,9 +26,9 @@ namespace NzbDrone.Common.Processes
Boolean Exists(int processId); Boolean Exists(int processId);
Boolean Exists(string processName); Boolean Exists(string processName);
ProcessPriorityClass GetCurrentProcessPriority(); ProcessPriorityClass GetCurrentProcessPriority();
Process Start(string path, string args = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null); Process Start(string path, string args = null, StringDictionary environmentVariables = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null);
Process SpawnNewProcess(string path, string args = null); Process SpawnNewProcess(string path, string args = null, StringDictionary environmentVariables = null);
ProcessOutput StartAndCapture(string path, string args = null); ProcessOutput StartAndCapture(string path, string args = null, StringDictionary environmentVariables = null);
} }
public class ProcessProvider : IProcessProvider public class ProcessProvider : IProcessProvider
@ -104,7 +106,7 @@ namespace NzbDrone.Common.Processes
process.Start(); process.Start();
} }
public Process Start(string path, string args = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null) public Process Start(string path, string args = null, StringDictionary environmentVariables = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null)
{ {
if (OsInfo.IsMonoRuntime && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase)) if (OsInfo.IsMonoRuntime && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
{ {
@ -123,6 +125,13 @@ namespace NzbDrone.Common.Processes
RedirectStandardInput = true RedirectStandardInput = true
}; };
if (environmentVariables != null)
{
foreach (DictionaryEntry environmentVariable in environmentVariables)
{
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
}
}
logger.Debug("Starting {0} {1}", path, args); logger.Debug("Starting {0} {1}", path, args);
@ -163,7 +172,7 @@ namespace NzbDrone.Common.Processes
return process; return process;
} }
public Process SpawnNewProcess(string path, string args = null) public Process SpawnNewProcess(string path, string args = null, StringDictionary environmentVariables = null)
{ {
if (OsInfo.IsMonoRuntime && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase)) if (OsInfo.IsMonoRuntime && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
{ {
@ -184,10 +193,14 @@ namespace NzbDrone.Common.Processes
return process; return process;
} }
public ProcessOutput StartAndCapture(string path, string args = null) public ProcessOutput StartAndCapture(string path, string args = null, StringDictionary environmentVariables = null)
{ {
var output = new ProcessOutput(); var output = new ProcessOutput();
Start(path, args, s => output.Standard.Add(s), error => output.Error.Add(error)).WaitForExit(); var process = Start(path, args, environmentVariables, s => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Standard, s)),
error => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Error, error)));
process.WaitForExit();
output.ExitCode = process.ExitCode;
return output; return output;
} }

@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.NotificationTests
{ {
(Subject.Definition.Settings as SynologyIndexerSettings).UpdateLibrary = false; (Subject.Definition.Settings as SynologyIndexerSettings).UpdateLibrary = false;
Subject.AfterRename(_series); Subject.OnRename(_series);
Mocker.GetMock<ISynologyIndexerProxy>() Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.UpdateFolder(_series.Path), Times.Never()); .Verify(v => v.UpdateFolder(_series.Path), Times.Never());
@ -93,7 +93,7 @@ namespace NzbDrone.Core.Test.NotificationTests
[Test] [Test]
public void should_update_entire_series_folder_on_rename() public void should_update_entire_series_folder_on_rename()
{ {
Subject.AfterRename(_series); Subject.OnRename(_series);
Mocker.GetMock<ISynologyIndexerProxy>() Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.UpdateFolder(@"C:\Test\".AsOsAgnostic()), Times.Once()); .Verify(v => v.UpdateFolder(@"C:\Test\".AsOsAgnostic()), Times.Once());

@ -145,7 +145,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.Execute(new ApplicationUpdateCommand()); Subject.Execute(new ApplicationUpdateCommand());
Mocker.GetMock<IProcessProvider>() Mocker.GetMock<IProcessProvider>()
.Verify(c => c.Start(It.IsAny<string>(), It.Is<String>(s => s.StartsWith("12")), null, null), Times.Once()); .Verify(c => c.Start(It.IsAny<string>(), It.Is<String>(s => s.StartsWith("12")), null, null, null), Times.Once());
} }
[Test] [Test]
@ -179,7 +179,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.Execute(new ApplicationUpdateCommand()); Subject.Execute(new ApplicationUpdateCommand());
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Once()); Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null, null), Times.Once());
} }
[Test] [Test]
@ -193,7 +193,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand())); Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
ExceptionVerification.ExpectedErrors(1); ExceptionVerification.ExpectedErrors(1);
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never()); Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null, null), Times.Never());
} }
[Test] [Test]
@ -207,7 +207,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand())); Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
ExceptionVerification.ExpectedErrors(1); ExceptionVerification.ExpectedErrors(1);
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never()); Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null, null), Times.Never());
} }
[Test] [Test]
@ -225,7 +225,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand())); Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
ExceptionVerification.ExpectedErrors(1); ExceptionVerification.ExpectedErrors(1);
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never()); Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null, null), Times.Never());
} }
[Test] [Test]

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Annotations namespace NzbDrone.Core.Annotations
{ {
@ -10,10 +11,10 @@ namespace NzbDrone.Core.Annotations
Order = order; Order = order;
} }
public Int32 Order { get; private set; } public int Order { get; private set; }
public String Label { get; set; } public string Label { get; set; }
public String HelpText { get; set; } public string HelpText { get; set; }
public String HelpLink { get; set; } public string HelpLink { get; set; }
public FieldType Type { get; set; } public FieldType Type { get; set; }
public Boolean Advanced { get; set; } public Boolean Advanced { get; set; }
public Type SelectOptions { get; set; } public Type SelectOptions { get; set; }

@ -0,0 +1,21 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(87)]
public class add_on_rename_to_notifcations : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Notifications").AddColumn("OnRename").AsBoolean().Nullable();
Execute.Sql("UPDATE Notifications SET OnRename = OnDownload WHERE Implementation IN ('PlexServer', 'Xbmc', 'MediaBrowser')");
Execute.Sql("UPDATE Notifications SET OnRename = 0 WHERE Implementation NOT IN ('PlexServer', 'Xbmc', 'MediaBrowser')");
Alter.Table("Notifications").AlterColumn("OnRename").AsBoolean().NotNullable();
Execute.Sql("UPDATE Notifications SET OnGrab = 0 WHERE Implementation = 'PlexServer'");
}
}
}

@ -0,0 +1,21 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(89)]
public class add_on_rename_to_notifcations : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Notifications").AddColumn("OnRename").AsBoolean().Nullable();
Execute.Sql("UPDATE Notifications SET OnRename = OnDownload WHERE Implementation IN ('PlexServer', 'Xbmc', 'MediaBrowser')");
Execute.Sql("UPDATE Notifications SET OnRename = 0 WHERE Implementation NOT IN ('PlexServer', 'Xbmc', 'MediaBrowser')");
Alter.Table("Notifications").AlterColumn("OnRename").AsBoolean().NotNullable();
Execute.Sql("UPDATE Notifications SET OnGrab = 0 WHERE Implementation = 'PlexServer'");
}
}
}

@ -53,7 +53,12 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.SupportsRss) .Ignore(i => i.SupportsRss)
.Ignore(i => i.SupportsSearch); .Ignore(i => i.SupportsSearch);
Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications"); Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications")
.Ignore(i => i.SupportsOnGrab)
.Ignore(i => i.SupportsOnDownload)
.Ignore(i => i.SupportsOnUpgrade)
.Ignore(i => i.SupportsOnRename);
Mapper.Entity<MetadataDefinition>().RegisterDefinition("Metadata"); Mapper.Entity<MetadataDefinition>().RegisterDefinition("Metadata");
Mapper.Entity<DownloadClientDefinition>().RegisterDefinition("DownloadClients") Mapper.Entity<DownloadClientDefinition>().RegisterDefinition("DownloadClients")

@ -0,0 +1,58 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Notifications.CustomScript
{
public class CustomScript : NotificationBase<CustomScriptSettings>
{
private readonly ICustomScriptService _customScriptService;
public CustomScript(ICustomScriptService customScriptService)
{
_customScriptService = customScriptService;
}
public override string Link
{
get { return "https://github.com/Sonarr/Sonarr/wiki/Custom-Post-Processing-Scripts"; }
}
public override void OnGrab(string message)
{
}
public override void OnDownload(DownloadMessage message)
{
_customScriptService.OnDownload(message.Series, message.EpisodeFile, Settings);
}
public override void OnRename(Series series)
{
_customScriptService.OnRename(series, Settings);
}
public override string Name
{
get
{
return "Custom Script";
}
}
public override bool SupportsOnGrab
{
get
{
return false;
}
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
return new ValidationResult(failures);
}
}
}

@ -0,0 +1,92 @@
using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Processes;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.CustomScript
{
public interface ICustomScriptService
{
void OnDownload(Series series, EpisodeFile episodeFile, CustomScriptSettings settings);
void OnRename(Series series, CustomScriptSettings settings);
ValidationFailure Test(CustomScriptSettings settings);
}
public class CustomScriptService : ICustomScriptService
{
private readonly IProcessProvider _processProvider;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public CustomScriptService(IProcessProvider processProvider, IDiskProvider diskProvider, Logger logger)
{
_processProvider = processProvider;
_diskProvider = diskProvider;
_logger = logger;
}
public void OnDownload(Series series, EpisodeFile episodeFile, CustomScriptSettings settings)
{
var environmentVariables = new StringDictionary();
environmentVariables.Add("Sonarr.EventType", "Download");
environmentVariables.Add("Sonarr.Series.Id", series.Id.ToString());
environmentVariables.Add("Sonarr.Series.Title", series.Title);
environmentVariables.Add("Sonarr.Series.Path", series.Path);
environmentVariables.Add("Sonarr.Series.TvdbId", series.TvdbId.ToString());
environmentVariables.Add("Sonarr.EpisodeFile.Id", episodeFile.Id.ToString());
environmentVariables.Add("Sonarr.EpisodeFile.RelativePath", episodeFile.RelativePath);
environmentVariables.Add("Sonarr.EpisodeFile.Path", Path.Combine(series.Path, episodeFile.RelativePath));
environmentVariables.Add("Sonarr.EpisodeFile.SeasonNumber", episodeFile.SeasonNumber.ToString());
environmentVariables.Add("Sonarr.EpisodeFile.EpisodeNumbers", String.Join(",", episodeFile.Episodes.Value.Select(e => e.EpisodeNumber)));
environmentVariables.Add("Sonarr.EpisodeFile.EpisodeAirDates", String.Join(",", episodeFile.Episodes.Value.Select(e => e.AirDate)));
environmentVariables.Add("Sonarr.EpisodeFile.EpisodeAirDatesUtc", String.Join(",", episodeFile.Episodes.Value.Select(e => e.AirDateUtc)));
environmentVariables.Add("Sonarr.EpisodeFile.Quality", episodeFile.Quality.Quality.Name);
environmentVariables.Add("Sonarr.EpisodeFile.QualityVersion", episodeFile.Quality.Revision.Version.ToString());
environmentVariables.Add("Sonarr.EpisodeFile.ReleaseGroup", episodeFile.ReleaseGroup ?? String.Empty);
environmentVariables.Add("Sonarr.EpisodeFile.SceneName", episodeFile.SceneName ?? String.Empty);
ExecuteScript(environmentVariables, settings);
}
public void OnRename(Series series, CustomScriptSettings settings)
{
var environmentVariables = new StringDictionary();
environmentVariables.Add("Sonarr.EventType", "Rename");
environmentVariables.Add("Sonarr.Series.Id", series.Id.ToString());
environmentVariables.Add("Sonarr.Series.Title", series.Title);
environmentVariables.Add("Sonarr.Series.Path", series.Path);
environmentVariables.Add("Sonarr.Series.TvdbId", series.TvdbId.ToString());
ExecuteScript(environmentVariables, settings);
}
public ValidationFailure Test(CustomScriptSettings settings)
{
if (!_diskProvider.FileExists(settings.Path))
{
return new NzbDroneValidationFailure("Path", "File does not exist");
}
return null;
}
private void ExecuteScript(StringDictionary environmentVariables, CustomScriptSettings settings)
{
_logger.Debug("Executing external script: {0}", settings.Path);
var process = _processProvider.StartAndCapture(settings.Path, settings.Arguments, environmentVariables);
_logger.Debug("Executed external script: {0} - Status: {1}", settings.Path, process.ExitCode);
_logger.Debug("Script Output: \r\n{0}", String.Join("\r\n", process.Lines));
}
}
}

@ -0,0 +1,33 @@
using System;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Notifications.CustomScript
{
public class CustomScriptSettingsValidator : AbstractValidator<CustomScriptSettings>
{
public CustomScriptSettingsValidator()
{
RuleFor(c => c.Path).IsValidPath();
}
}
public class CustomScriptSettings : IProviderConfig
{
private static readonly CustomScriptSettingsValidator Validator = new CustomScriptSettingsValidator();
[FieldDefinition(0, Label = "Path", Type = FieldType.Path)]
public String Path { get; set; }
[FieldDefinition(0, Label = "Arguments", HelpText = "Arguments to pass to the script")]
public String Arguments { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

@ -36,7 +36,7 @@ namespace NzbDrone.Core.Notifications.Email
_emailService.SendEmail(Settings, subject, body); _emailService.SendEmail(Settings, subject, body);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -48,6 +48,14 @@ namespace NzbDrone.Core.Notifications.Email
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Growl
_growlService.SendNotification(title, message.Message, "DOWNLOAD", Settings.Host, Settings.Port, Settings.Password); _growlService.SendNotification(title, message.Message, "DOWNLOAD", Settings.Host, Settings.Port, Settings.Password);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -45,6 +45,14 @@ namespace NzbDrone.Core.Notifications.Growl
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -9,6 +9,10 @@ namespace NzbDrone.Core.Notifications
void OnGrab(string message); void OnGrab(string message);
void OnDownload(DownloadMessage message); void OnDownload(DownloadMessage message);
void AfterRename(Series series); void OnRename(Series series);
bool SupportsOnGrab { get; }
bool SupportsOnDownload { get; }
bool SupportsOnUpgrade { get; }
bool SupportsOnRename { get; }
} }
} }

@ -45,7 +45,7 @@ namespace NzbDrone.Core.Notifications.MediaBrowser
} }
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
if (Settings.UpdateLibrary) if (Settings.UpdateLibrary)
{ {

@ -40,8 +40,13 @@ namespace NzbDrone.Core.Notifications
public abstract string Link { get; } public abstract string Link { get; }
public abstract void OnGrab(string message); public abstract void OnGrab(string message);
public abstract void OnDownload(DownloadMessage message); public abstract void OnDownload(DownloadMessage message);
public abstract void AfterRename(Series series); public abstract void OnRename(Series series);
public virtual bool SupportsOnGrab { get { return true; } }
public virtual bool SupportsOnDownload { get { return true; } }
public virtual bool SupportsOnUpgrade { get { return true; } }
public virtual bool SupportsOnRename { get { return true; } }
protected TSettings Settings protected TSettings Settings
{ {

@ -11,12 +11,17 @@ namespace NzbDrone.Core.Notifications
Tags = new HashSet<Int32>(); Tags = new HashSet<Int32>();
} }
public Boolean OnGrab { get; set; } public bool OnGrab { get; set; }
public Boolean OnDownload { get; set; } public bool OnDownload { get; set; }
public Boolean OnUpgrade { get; set; } public bool OnUpgrade { get; set; }
public HashSet<Int32> Tags { get; set; } public bool OnRename { get; set; }
public bool SupportsOnGrab { get; set; }
public bool SupportsOnDownload { get; set; }
public bool SupportsOnUpgrade { get; set; }
public bool SupportsOnRename { get; set; }
public HashSet<int> Tags { get; set; }
public override Boolean Enable public override bool Enable
{ {
get get
{ {

@ -12,6 +12,7 @@ namespace NzbDrone.Core.Notifications
List<INotification> OnGrabEnabled(); List<INotification> OnGrabEnabled();
List<INotification> OnDownloadEnabled(); List<INotification> OnDownloadEnabled();
List<INotification> OnUpgradeEnabled(); List<INotification> OnUpgradeEnabled();
List<INotification> OnRenameEnabled();
} }
public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory
@ -35,5 +36,22 @@ namespace NzbDrone.Core.Notifications
{ {
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnUpgrade).ToList(); return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnUpgrade).ToList();
} }
public List<INotification> OnRenameEnabled()
{
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnRename).ToList();
}
public override NotificationDefinition GetProviderCharacteristics(INotification provider, NotificationDefinition definition)
{
definition = base.GetProviderCharacteristics(provider, definition);
definition.SupportsOnGrab = provider.SupportsOnGrab;
definition.SupportsOnDownload = provider.SupportsOnDownload;
definition.SupportsOnUpgrade = provider.SupportsOnUpgrade;
definition.SupportsOnRename = provider.SupportsOnRename;
return definition;
}
} }
} }

@ -137,19 +137,19 @@ namespace NzbDrone.Core.Notifications
public void Handle(SeriesRenamedEvent message) public void Handle(SeriesRenamedEvent message)
{ {
foreach (var notification in _notificationFactory.OnDownloadEnabled()) foreach (var notification in _notificationFactory.OnRenameEnabled())
{ {
try try
{ {
if (ShouldHandleSeries(notification.Definition, message.Series)) if (ShouldHandleSeries(notification.Definition, message.Series))
{ {
notification.AfterRename(message.Series); notification.OnRename(message.Series);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.WarnException("Unable to send AfterRename notification to: " + notification.Definition.Name, ex); _logger.WarnException("Unable to send OnRename notification to: " + notification.Definition.Name, ex);
} }
} }
} }

@ -34,7 +34,7 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid
_proxy.SendNotification(title, message.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); _proxy.SendNotification(title, message.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -46,6 +46,14 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -31,7 +31,7 @@ namespace NzbDrone.Core.Notifications.Plex
_plexClientService.Notify(Settings, header, message.Message); _plexClientService.Notify(Settings, header, message.Message);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -43,6 +43,14 @@ namespace NzbDrone.Core.Notifications.Plex
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -39,7 +39,7 @@ namespace NzbDrone.Core.Notifications.Plex
Notify(Settings, header, message.Message); Notify(Settings, header, message.Message);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -52,6 +52,14 @@ namespace NzbDrone.Core.Notifications.Plex
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -28,7 +28,7 @@ namespace NzbDrone.Core.Notifications.Plex
UpdateIfEnabled(message.Series); UpdateIfEnabled(message.Series);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
UpdateIfEnabled(series); UpdateIfEnabled(series);
} }
@ -49,6 +49,14 @@ namespace NzbDrone.Core.Notifications.Plex
} }
} }
public override bool SupportsOnGrab
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -34,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Prowl
_prowlService.SendNotification(title, message.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority); _prowlService.SendNotification(title, message.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -46,6 +46,14 @@ namespace NzbDrone.Core.Notifications.Prowl
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.PushBullet
_proxy.SendNotification(title, message.Message, Settings); _proxy.SendNotification(title, message.Message, Settings);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -45,6 +45,14 @@ namespace NzbDrone.Core.Notifications.PushBullet
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -34,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Pushalot
_proxy.SendNotification(title, message.Message, Settings); _proxy.SendNotification(title, message.Message, Settings);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -46,6 +46,14 @@ namespace NzbDrone.Core.Notifications.Pushalot
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Pushover
_proxy.SendNotification(title, message.Message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority, Settings.Sound); _proxy.SendNotification(title, message.Message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority, Settings.Sound);
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
} }
@ -45,6 +45,14 @@ namespace NzbDrone.Core.Notifications.Pushover
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -45,7 +45,7 @@ namespace NzbDrone.Core.Notifications.Synology
} }
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
if (Settings.UpdateLibrary) if (Settings.UpdateLibrary)
{ {
@ -61,6 +61,14 @@ namespace NzbDrone.Core.Notifications.Synology
} }
} }
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

@ -40,7 +40,7 @@ namespace NzbDrone.Core.Notifications.Xbmc
UpdateAndClean(message.Series, message.OldFiles.Any()); UpdateAndClean(message.Series, message.OldFiles.Any());
} }
public override void AfterRename(Series series) public override void OnRename(Series series)
{ {
UpdateAndClean(series); UpdateAndClean(series);
} }

@ -254,6 +254,7 @@
<Compile Include="Datastore\Migration\074_disable_eztv.cs" /> <Compile Include="Datastore\Migration\074_disable_eztv.cs" />
<Compile Include="Datastore\Migration\073_clear_ratings.cs" /> <Compile Include="Datastore\Migration\073_clear_ratings.cs" />
<Compile Include="Datastore\Migration\077_add_add_options_to_series.cs" /> <Compile Include="Datastore\Migration\077_add_add_options_to_series.cs" />
<Compile Include="Datastore\Migration\089_add_on_rename_to_notifcations.cs" />
<Compile Include="Datastore\Migration\085_expand_transmission_urlbase.cs" /> <Compile Include="Datastore\Migration\085_expand_transmission_urlbase.cs" />
<Compile Include="Datastore\Migration\086_pushbullet_device_ids.cs" /> <Compile Include="Datastore\Migration\086_pushbullet_device_ids.cs" />
<Compile Include="Datastore\Migration\081_move_dot_prefix_to_transmission_category.cs" /> <Compile Include="Datastore\Migration\081_move_dot_prefix_to_transmission_category.cs" />
@ -710,6 +711,9 @@
<Compile Include="Notifications\Plex\Models\PlexSectionItem.cs" /> <Compile Include="Notifications\Plex\Models\PlexSectionItem.cs" />
<Compile Include="Notifications\Plex\Models\PlexSection.cs" /> <Compile Include="Notifications\Plex\Models\PlexSection.cs" />
<Compile Include="Notifications\Plex\PlexAuthenticationException.cs" /> <Compile Include="Notifications\Plex\PlexAuthenticationException.cs" />
<Compile Include="Notifications\CustomScript\CustomScript.cs" />
<Compile Include="Notifications\CustomScript\CustomScriptService.cs" />
<Compile Include="Notifications\CustomScript\CustomScriptSettings.cs" />
<Compile Include="Notifications\Plex\PlexHomeTheater.cs" /> <Compile Include="Notifications\Plex\PlexHomeTheater.cs" />
<Compile Include="Notifications\Plex\PlexHomeTheaterSettings.cs" /> <Compile Include="Notifications\Plex\PlexHomeTheaterSettings.cs" />
<Compile Include="Notifications\Plex\PlexClientService.cs" /> <Compile Include="Notifications\Plex\PlexClientService.cs" />

@ -56,11 +56,11 @@ namespace NzbDrone.Host.AccessControl
if (output == null || !output.Standard.Any()) return false; if (output == null || !output.Standard.Any()) return false;
var hashLine = output.Standard.SingleOrDefault(line => CertificateHashRegex.IsMatch(line)); var hashLine = output.Standard.SingleOrDefault(line => CertificateHashRegex.IsMatch(line.Content));
if (hashLine != null) if (hashLine != null)
{ {
var match = CertificateHashRegex.Match(hashLine); var match = CertificateHashRegex.Match(hashLine.Content);
if (match.Success) if (match.Success)
{ {
@ -73,7 +73,7 @@ namespace NzbDrone.Host.AccessControl
} }
} }
return output.Standard.Any(line => line.Contains(ipPort)); return output.Standard.Any(line => line.Content.Contains(ipPort));
} }
private void Unregister() private void Unregister()

@ -139,7 +139,7 @@ namespace NzbDrone.Host.AccessControl
return output.Standard.Select(line => return output.Standard.Select(line =>
{ {
var match = UrlAclRegex.Match(line); var match = UrlAclRegex.Match(line.Content);
if (match.Success) if (match.Success)
{ {

@ -85,7 +85,7 @@ namespace NzbDrone.Test.Common
private void Start(string outputNzbdroneConsoleExe) private void Start(string outputNzbdroneConsoleExe)
{ {
var args = "-nobrowser -data=\"" + AppData + "\""; var args = "-nobrowser -data=\"" + AppData + "\"";
_nzbDroneProcess = _processProvider.Start(outputNzbdroneConsoleExe, args, OnOutputDataReceived, OnOutputDataReceived); _nzbDroneProcess = _processProvider.Start(outputNzbdroneConsoleExe, args, null, OnOutputDataReceived, OnOutputDataReceived);
} }

@ -32,7 +32,7 @@ namespace NzbDrone.Update.Test
Subject.Start(AppType.Service, targetFolder); Subject.Start(AppType.Service, targetFolder);
Mocker.GetMock<IProcessProvider>().Verify(c => c.SpawnNewProcess("c:\\NzbDrone\\NzbDrone.Console.exe", "/" + StartupContext.NO_BROWSER), Times.Once()); Mocker.GetMock<IProcessProvider>().Verify(c => c.SpawnNewProcess("c:\\NzbDrone\\NzbDrone.Console.exe", "/" + StartupContext.NO_BROWSER, null), Times.Once());
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);
} }

@ -27,9 +27,10 @@ module.exports = Marionette.ItemView.extend({
this.model.set({ this.model.set({
id : undefined, id : undefined,
onGrab : true, onGrab : this.model.get('supportsOnGrab'),
onDownload : true, onDownload : this.model.get('supportsOnDownload'),
onUpgrade : true onUpgrade : this.model.get('supportsOnUpgrade'),
onRename : this.model.get('supportsOnRename')
}); });
var editView = new EditView({ var editView = new EditView({
@ -47,9 +48,10 @@ module.exports = Marionette.ItemView.extend({
this.model.set({ this.model.set({
id : undefined, id : undefined,
onGrab : true, onGrab : this.model.get('supportsOnGrab'),
onDownload : true, onDownload : this.model.get('supportsOnDownload'),
onUpgrade : true onUpgrade : this.model.get('supportsOnUpgrade'),
onRename : this.model.get('supportsOnRename')
}); });
var editView = new EditView({ var editView = new EditView({

@ -6,6 +6,7 @@ var AsValidatedView = require('../../../Mixins/AsValidatedView');
var AsEditModalView = require('../../../Mixins/AsEditModalView'); var AsEditModalView = require('../../../Mixins/AsEditModalView');
require('../../../Form/FormBuilder'); require('../../../Form/FormBuilder');
require('../../../Mixins/TagInput'); require('../../../Mixins/TagInput');
require('../../../Mixins/FileBrowser');
require('bootstrap.tagsinput'); require('bootstrap.tagsinput');
var view = Marionette.ItemView.extend({ var view = Marionette.ItemView.extend({
@ -15,7 +16,9 @@ var view = Marionette.ItemView.extend({
onDownloadToggle : '.x-on-download', onDownloadToggle : '.x-on-download',
onUpgradeSection : '.x-on-upgrade', onUpgradeSection : '.x-on-upgrade',
tags : '.x-tags', tags : '.x-tags',
formTag : '.x-form-tag' modalBody : '.modal-body',
formTag : '.x-form-tag',
path : '.x-path'
}, },
events : { events : {
@ -43,6 +46,14 @@ var view = Marionette.ItemView.extend({
}); });
}, },
onShow : function() {
if (this.ui.path.length > 0) {
this.ui.modalBody.addClass('modal-overflow');
}
this.ui.path.fileBrowser();
},
_onAfterSave : function() { _onAfterSave : function() {
this.targetCollection.add(this.model, { merge : true }); this.targetCollection.add(this.model, { merge : true });
vent.trigger(vent.Commands.CloseModalCommand); vent.trigger(vent.Commands.CloseModalCommand);

@ -23,7 +23,7 @@
<div class="col-sm-5"> <div class="col-sm-5">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" name="onGrab"/> <input type="checkbox" name="onGrab" {{#unless supportsOnGrab}}disabled="disabled"{{/unless}}/>
<p> <p>
<span>Yes</span> <span>Yes</span>
<span>No</span> <span>No</span>
@ -45,7 +45,7 @@
<div class="col-sm-5"> <div class="col-sm-5">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" name="onDownload" class="x-on-download"/> <input type="checkbox" name="onDownload" class="x-on-download" {{#unless supportsOnDownload}}disabled="disabled"{{/unless}}/>
<p> <p>
<span>Yes</span> <span>Yes</span>
<span>No</span> <span>No</span>
@ -67,7 +67,7 @@
<div class="col-sm-5"> <div class="col-sm-5">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" name="onUpgrade"/> <input type="checkbox" name="onUpgrade" {{#unless supportsOnUpgrade}}disabled="disabled"{{/unless}}/>
<p> <p>
<span>Yes</span> <span>Yes</span>
<span>No</span> <span>No</span>
@ -83,6 +83,28 @@
</div> </div>
</div> </div>
<div class="form-group x-on-upgrade">
<label class="col-sm-3 control-label">On Rename</label>
<div class="col-sm-5">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="onRename" {{#unless supportsOnRename}}disabled="disabled"{{/unless}}/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Be notified when episodes are renamed"/>
</span>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Filter Series Tags</label> <label class="col-sm-3 control-label">Filter Series Tags</label>

@ -4,16 +4,44 @@
</div> </div>
<div class="settings"> <div class="settings">
{{#if onGrab}} {{#if supportsOnGrab}}
<span class="label label-success">On Grab</span> {{#if onGrab}}
<span class="label label-success">On Grab</span>
{{else}}
<span class="label label-default">On Grab</span>
{{/if}}
{{else}} {{else}}
<span class="label label-default">On Grab</span> <span class="label label-default label-disabled">On Grab</span>
{{/if}} {{/if}}
{{#if onDownload}} {{#if supportsOnDownload}}
<span class="label label-success">On Download</span> {{#if onDownload}}
<span class="label label-success">On Download</span>
{{else}}
<span class="label label-default">On Download</span>
{{/if}}
{{else}} {{else}}
<span class="label label-default">On Download</span> <span class="label label-default label-disabled">On Download</span>
{{/if}}
{{#if supportsOnUpgrade}}
{{#if onUpgrade}}
<span class="label label-success">On Upgrade</span>
{{else}}
<span class="label label-default">On Upgrade</span>
{{/if}}
{{else}}
<span class="label label-default label-disabled">On Upgrade</span>
{{/if}}
{{#if supportsOnRename}}
{{#if onRename}}
<span class="label label-success">On Rename</span>
{{else}}
<span class="label label-default">On Rename</span>
{{/if}}
{{else}}
<span class="label label-default label-disabled">On Rename</span>
{{/if}} {{/if}}
</div> </div>
</div> </div>

@ -10,11 +10,17 @@
.clickable; .clickable;
width: 290px; width: 290px;
height: 90px; height: 115px;
padding: 20px 20px; padding: 20px 20px;
.settings { .settings {
margin-top: 5px; margin-top: 5px;
.label {
display : inline-block;
margin-bottom : 2px;
padding : 4px 6px 3px 6px;
}
} }
&.add-card { &.add-card {

Loading…
Cancel
Save