Custom scripts

New: Run custom scripts (Connection)

Closes #439
This commit is contained in:
Mark McDowall 2015-05-20 16:22:10 -07:00
parent 492b114510
commit 0f2bba0615
42 changed files with 560 additions and 74 deletions

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
namespace NzbDrone.Api.Notifications
{
@ -9,6 +8,11 @@ namespace NzbDrone.Api.Notifications
public bool OnGrab { get; set; }
public bool OnDownload { 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 HashSet<int> Tags { get; set; }
}

View File

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

View File

@ -1,17 +1,32 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Common.Processes
{
public class ProcessOutput
{
public List<String> Standard { get; private set; }
public List<String> Error { get; private set; }
public int ExitCode { get; set; }
public List<ProcessOutputLine> Lines { get; set; }
public ProcessOutput()
{
Standard = new List<string>();
Error = new List<string>();
Lines = new List<ProcessOutputLine>();
}
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();
}
}
}
}

View File

@ -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
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
@ -24,9 +26,9 @@ namespace NzbDrone.Common.Processes
Boolean Exists(int processId);
Boolean Exists(string processName);
ProcessPriorityClass GetCurrentProcessPriority();
Process Start(string path, string args = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null);
Process SpawnNewProcess(string path, string args = null);
ProcessOutput StartAndCapture(string path, string args = 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, StringDictionary environmentVariables = null);
ProcessOutput StartAndCapture(string path, string args = null, StringDictionary environmentVariables = null);
}
public class ProcessProvider : IProcessProvider
@ -104,7 +106,7 @@ namespace NzbDrone.Common.Processes
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))
{
@ -123,6 +125,13 @@ namespace NzbDrone.Common.Processes
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);
@ -163,7 +172,7 @@ namespace NzbDrone.Common.Processes
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))
{
@ -184,10 +193,14 @@ namespace NzbDrone.Common.Processes
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();
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;
}

View File

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

View File

@ -145,7 +145,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.Execute(new ApplicationUpdateCommand());
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]
@ -179,7 +179,7 @@ namespace NzbDrone.Core.Test.UpdateTests
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]
@ -193,7 +193,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
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]
@ -207,7 +207,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
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]
@ -225,7 +225,7 @@ namespace NzbDrone.Core.Test.UpdateTests
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
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]

View File

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

View File

@ -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'");
}
}
}

View File

@ -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'");
}
}
}

View File

@ -53,7 +53,12 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.SupportsRss)
.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<DownloadClientDefinition>().RegisterDefinition("DownloadClients")

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -36,7 +36,7 @@ namespace NzbDrone.Core.Notifications.Email
_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()
{
var failures = new List<ValidationFailure>();

View File

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Growl
_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()
{
var failures = new List<ValidationFailure>();

View File

@ -9,6 +9,10 @@ namespace NzbDrone.Core.Notifications
void OnGrab(string 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; }
}
}

View File

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

View File

@ -40,8 +40,13 @@ namespace NzbDrone.Core.Notifications
public abstract string Link { get; }
public abstract void OnGrab(string message);
public abstract void OnDownload(DownloadMessage message);
public abstract void AfterRename(Series series);
public abstract void OnDownload(DownloadMessage message);
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
{

View File

@ -11,12 +11,17 @@ namespace NzbDrone.Core.Notifications
Tags = new HashSet<Int32>();
}
public Boolean OnGrab { get; set; }
public Boolean OnDownload { get; set; }
public Boolean OnUpgrade { get; set; }
public HashSet<Int32> Tags { get; set; }
public bool OnGrab { get; set; }
public bool OnDownload { 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 HashSet<int> Tags { get; set; }
public override Boolean Enable
public override bool Enable
{
get
{

View File

@ -12,6 +12,7 @@ namespace NzbDrone.Core.Notifications
List<INotification> OnGrabEnabled();
List<INotification> OnDownloadEnabled();
List<INotification> OnUpgradeEnabled();
List<INotification> OnRenameEnabled();
}
public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory
@ -35,5 +36,22 @@ namespace NzbDrone.Core.Notifications
{
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;
}
}
}

View File

@ -137,19 +137,19 @@ namespace NzbDrone.Core.Notifications
public void Handle(SeriesRenamedEvent message)
{
foreach (var notification in _notificationFactory.OnDownloadEnabled())
foreach (var notification in _notificationFactory.OnRenameEnabled())
{
try
{
if (ShouldHandleSeries(notification.Definition, message.Series))
{
notification.AfterRename(message.Series);
notification.OnRename(message.Series);
}
}
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);
}
}
}

View File

@ -34,7 +34,7 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid
_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()
{
var failures = new List<ValidationFailure>();

View File

@ -31,7 +31,7 @@ namespace NzbDrone.Core.Notifications.Plex
_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()
{
var failures = new List<ValidationFailure>();

View File

@ -39,7 +39,7 @@ namespace NzbDrone.Core.Notifications.Plex
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()
{
var failures = new List<ValidationFailure>();

View File

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

View File

@ -34,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Prowl
_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()
{
var failures = new List<ValidationFailure>();

View File

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.PushBullet
_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()
{
var failures = new List<ValidationFailure>();

View File

@ -34,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Pushalot
_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()
{
var failures = new List<ValidationFailure>();

View File

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Pushover
_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()
{
var failures = new List<ValidationFailure>();

View File

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

View File

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

View File

@ -254,6 +254,7 @@
<Compile Include="Datastore\Migration\074_disable_eztv.cs" />
<Compile Include="Datastore\Migration\073_clear_ratings.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\086_pushbullet_device_ids.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\PlexSection.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\PlexHomeTheaterSettings.cs" />
<Compile Include="Notifications\Plex\PlexClientService.cs" />

View File

@ -56,11 +56,11 @@ namespace NzbDrone.Host.AccessControl
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)
{
var match = CertificateHashRegex.Match(hashLine);
var match = CertificateHashRegex.Match(hashLine.Content);
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()

View File

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

View File

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

View File

@ -32,7 +32,7 @@ namespace NzbDrone.Update.Test
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);
}

View File

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

View File

@ -6,6 +6,7 @@ var AsValidatedView = require('../../../Mixins/AsValidatedView');
var AsEditModalView = require('../../../Mixins/AsEditModalView');
require('../../../Form/FormBuilder');
require('../../../Mixins/TagInput');
require('../../../Mixins/FileBrowser');
require('bootstrap.tagsinput');
var view = Marionette.ItemView.extend({
@ -15,7 +16,9 @@ var view = Marionette.ItemView.extend({
onDownloadToggle : '.x-on-download',
onUpgradeSection : '.x-on-upgrade',
tags : '.x-tags',
formTag : '.x-form-tag'
modalBody : '.modal-body',
formTag : '.x-form-tag',
path : '.x-path'
},
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() {
this.targetCollection.add(this.model, { merge : true });
vent.trigger(vent.Commands.CloseModalCommand);

View File

@ -23,7 +23,7 @@
<div class="col-sm-5">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="onGrab"/>
<input type="checkbox" name="onGrab" {{#unless supportsOnGrab}}disabled="disabled"{{/unless}}/>
<p>
<span>Yes</span>
<span>No</span>
@ -45,7 +45,7 @@
<div class="col-sm-5">
<div class="input-group">
<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>
<span>Yes</span>
<span>No</span>
@ -67,7 +67,7 @@
<div class="col-sm-5">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="onUpgrade"/>
<input type="checkbox" name="onUpgrade" {{#unless supportsOnUpgrade}}disabled="disabled"{{/unless}}/>
<p>
<span>Yes</span>
<span>No</span>
@ -83,6 +83,28 @@
</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">
<label class="col-sm-3 control-label">Filter Series Tags</label>

View File

@ -4,16 +4,44 @@
</div>
<div class="settings">
{{#if onGrab}}
<span class="label label-success">On Grab</span>
{{#if supportsOnGrab}}
{{#if onGrab}}
<span class="label label-success">On Grab</span>
{{else}}
<span class="label label-default">On Grab</span>
{{/if}}
{{else}}
<span class="label label-default">On Grab</span>
<span class="label label-default label-disabled">On Grab</span>
{{/if}}
{{#if onDownload}}
<span class="label label-success">On Download</span>
{{#if supportsOnDownload}}
{{#if onDownload}}
<span class="label label-success">On Download</span>
{{else}}
<span class="label label-default">On Download</span>
{{/if}}
{{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}}
</div>
</div>

View File

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