From c91fabcf2d14afda7dc689c8e2a73edba50c2639 Mon Sep 17 00:00:00 2001 From: Robin Dadswell <19610103+RobinDadswell@users.noreply.github.com> Date: Sun, 24 Jan 2021 09:21:29 +0000 Subject: [PATCH] New: On Delete Notifications Closes #2410 --- .../EditNotificationModalContent.js | 30 +++++++++++ .../Notifications/Notification.js | 54 ++++++++++++++----- .../Store/Actions/Settings/notifications.js | 2 + .../NotificationBaseFixture.cs | 13 ++++- .../149_add_on_delete_to_notifications.cs | 16 ++++++ src/NzbDrone.Core/Datastore/TableMapping.cs | 2 + .../MediaFiles/Events/DeleteCompletedEvent.cs | 9 ++++ .../MediaFiles/MediaFileDeletionService.cs | 6 +++ .../Notifications/Boxcar/Boxcar.cs | 10 ++++ .../CustomScript/CustomScript.cs | 50 +++++++++++++++++ .../Notifications/Discord/Discord.cs | 36 +++++++++++++ .../Notifications/Email/Email.cs | 14 +++++ .../Notifications/EpisodeDeleteMessage.cs | 20 +++++++ .../Notifications/Gotify/Gotify.cs | 10 ++++ .../Notifications/INotification.cs | 4 ++ src/NzbDrone.Core/Notifications/Join/Join.cs | 9 ++++ .../Notifications/NotificationBase.cs | 17 ++++++ .../Notifications/NotificationDefinition.cs | 6 ++- .../Notifications/NotificationFactory.cs | 14 +++++ .../Notifications/NotificationService.cs | 53 ++++++++++++++++++ .../Notifications/Plex/Server/PlexServer.cs | 13 +++++ .../Notifications/Prowl/Prowl.cs | 10 ++++ .../Notifications/PushBullet/PushBullet.cs | 9 ++++ .../Notifications/Pushover/Pushover.cs | 10 ++++ .../Notifications/SendGrid/SendGrid.cs | 9 ++++ .../Notifications/SeriesDeleteMessage.cs | 28 ++++++++++ .../Notifications/Slack/Slack.cs | 48 +++++++++++++++++ .../Notifications/Synology/SynologyIndexer.cs | 19 +++++++ .../Notifications/Telegram/Telegram.cs | 10 ++++ .../Notifications/Twitter/Twitter.cs | 10 ++++ .../Notifications/Webhook/Webhook.cs | 21 ++++++++ .../Webhook/WebhookEpisodeDeletePayload.cs | 11 ++++ .../Notifications/Webhook/WebhookEventType.cs | 1 + .../Webhook/WebhookSeriesDeletePayload.cs | 11 ++++ src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs | 18 +++++++ .../Notifications/NotificationResource.cs | 12 +++++ 36 files changed, 601 insertions(+), 14 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/149_add_on_delete_to_notifications.cs create mode 100644 src/NzbDrone.Core/MediaFiles/Events/DeleteCompletedEvent.cs create mode 100644 src/NzbDrone.Core/Notifications/EpisodeDeleteMessage.cs create mode 100644 src/NzbDrone.Core/Notifications/SeriesDeleteMessage.cs create mode 100644 src/NzbDrone.Core/Notifications/Webhook/WebhookEpisodeDeletePayload.cs create mode 100644 src/NzbDrone.Core/Notifications/Webhook/WebhookSeriesDeletePayload.cs diff --git a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js index 4deb2e78c..0b993942a 100644 --- a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js +++ b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js @@ -42,11 +42,15 @@ function EditNotificationModalContent(props) { onDownload, onUpgrade, onRename, + onSeriesDelete, + onEpisodeFileDelete, onHealthIssue, supportsOnGrab, supportsOnDownload, supportsOnUpgrade, supportsOnRename, + supportsOnSeriesDelete, + supportsOnEpisodeFileDelete, supportsOnHealthIssue, includeHealthWarnings, tags, @@ -150,6 +154,32 @@ function EditNotificationModalContent(props) { /> + + On Series Delete + + + + + + On Episode File Delete + + + + On Health Issue diff --git a/frontend/src/Settings/Notifications/Notifications/Notification.js b/frontend/src/Settings/Notifications/Notifications/Notification.js index ecea5b6db..30ec1777c 100644 --- a/frontend/src/Settings/Notifications/Notifications/Notification.js +++ b/frontend/src/Settings/Notifications/Notifications/Notification.js @@ -58,11 +58,15 @@ class Notification extends Component { onDownload, onUpgrade, onRename, + onSeriesDelete, + onEpisodeFileDelete, onHealthIssue, supportsOnGrab, supportsOnDownload, supportsOnUpgrade, supportsOnRename, + supportsOnSeriesDelete, + supportsOnEpisodeFileDelete, supportsOnHealthIssue } = this.props; @@ -77,48 +81,70 @@ class Notification extends Component { { - supportsOnGrab && onGrab && + supportsOnGrab && onGrab ? + : + null } { - supportsOnDownload && onDownload && + supportsOnDownload && onDownload ? + : + null } { - supportsOnUpgrade && onDownload && onUpgrade && + supportsOnUpgrade && onDownload && onUpgrade ? + : + null } { - supportsOnRename && onRename && + supportsOnRename && onRename ? + : + null } { - supportsOnHealthIssue && onHealthIssue && + supportsOnHealthIssue && onHealthIssue ? + : + null } { - !onGrab && !onDownload && !onRename && !onHealthIssue && + supportsOnSeriesDelete && onSeriesDelete ? + : + null + } + + { + supportsOnEpisodeFileDelete && onEpisodeFileDelete ? + : + null + } + + { + !onGrab && !onDownload && !onRename && !onHealthIssue && !onSeriesDelete && !onEpisodeFileDelete ? + : + null } i.SupportsOnDownload) .Ignore(i => i.SupportsOnUpgrade) .Ignore(i => i.SupportsOnRename) + .Ignore(i => i.SupportsOnSeriesDelete) + .Ignore(i => i.SupportsOnEpisodeFileDelete) .Ignore(i => i.SupportsOnHealthIssue); Mapper.Entity().RegisterDefinition("Metadata") diff --git a/src/NzbDrone.Core/MediaFiles/Events/DeleteCompletedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/DeleteCompletedEvent.cs new file mode 100644 index 000000000..883ac4df9 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Events/DeleteCompletedEvent.cs @@ -0,0 +1,9 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.MediaFiles.Events +{ + public class DeleteCompletedEvent : IEvent + { + + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs index b8c4b7ee0..00fdc8254 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileDeletionService.cs @@ -28,6 +28,7 @@ namespace NzbDrone.Core.MediaFiles private readonly IMediaFileService _mediaFileService; private readonly ISeriesService _seriesService; private readonly IConfigService _configService; + private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; public MediaFileDeletionService(IDiskProvider diskProvider, @@ -35,6 +36,7 @@ namespace NzbDrone.Core.MediaFiles IMediaFileService mediaFileService, ISeriesService seriesService, IConfigService configService, + IEventAggregator eventAggregator, Logger logger) { _diskProvider = diskProvider; @@ -42,6 +44,7 @@ namespace NzbDrone.Core.MediaFiles _mediaFileService = mediaFileService; _seriesService = seriesService; _configService = configService; + _eventAggregator = eventAggregator; _logger = logger; } @@ -81,6 +84,8 @@ namespace NzbDrone.Core.MediaFiles // Delete the episode file from the database to clean it up even if the file was already deleted _mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual); + + _eventAggregator.PublishEvent(new DeleteCompletedEvent()); } public void HandleAsync(SeriesDeletedEvent message) @@ -111,6 +116,7 @@ namespace NzbDrone.Core.MediaFiles { _recycleBinProvider.DeleteFolder(message.Series.Path); } + _eventAggregator.PublishEvent(new DeleteCompletedEvent()); } } diff --git a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs b/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs index 125534fd2..326bc85af 100644 --- a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs +++ b/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs @@ -26,6 +26,16 @@ namespace NzbDrone.Core.Notifications.Boxcar _proxy.SendNotification(EPISODE_DOWNLOADED_TITLE , message.Message, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck message) { _proxy.SendNotification(HEALTH_ISSUE_TITLE, message.Message, Settings); diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index 32e3e8b34..7b6aff744 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -127,6 +127,56 @@ namespace NzbDrone.Core.Notifications.CustomScript ExecuteScript(environmentVariables); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + var series = deleteMessage.Series; + var episodeFile = deleteMessage.EpisodeFile; + + var environmentVariables = new StringDictionary(); + + environmentVariables.Add("Sonarr_EventType", "EpisodeDeleted"); + 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_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_Id", episodeFile.Id.ToString()); + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeCount", episodeFile.Episodes.Value.Count.ToString()); + environmentVariables.Add("Sonarr_EpisodeFile_RelativePath", episodeFile.RelativePath); + environmentVariables.Add("Sonarr_EpisodeFile_Path", Path.Combine(series.Path, episodeFile.RelativePath)); + environmentVariables.Add("Sonarr_EpisodeFile_EpisodeIds", string.Join(",", episodeFile.Episodes.Value.Select(e => e.Id))); + 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_EpisodeTitles", string.Join("|", episodeFile.Episodes.Value.Select(e => e.Title))); + 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); + + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + var series = deleteMessage.Series; + var environmentVariables = new StringDictionary(); + + environmentVariables.Add("Sonarr_EventType", "SeriesDeleted"); + 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_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_Series_DeletedFiles", deleteMessage.DeletedFiles.ToString()); + + ExecuteScript(environmentVariables); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { var environmentVariables = new StringDictionary(); diff --git a/src/NzbDrone.Core/Notifications/Discord/Discord.cs b/src/NzbDrone.Core/Notifications/Discord/Discord.cs index 5f68d29c9..062103288 100644 --- a/src/NzbDrone.Core/Notifications/Discord/Discord.cs +++ b/src/NzbDrone.Core/Notifications/Discord/Discord.cs @@ -235,6 +235,42 @@ namespace NzbDrone.Core.Notifications.Discord _proxy.SendPayload(payload, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + var series = deleteMessage.Series; + var episodes = deleteMessage.EpisodeFile.Episodes; + + var attachments = new List + { + new Embed + { + Title = GetTitle(series, episodes) + } + }; + + var payload = CreatePayload("Episode Deleted", attachments); + + _proxy.SendPayload(payload, Settings); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + var series = deleteMessage.Series; + + var attachments = new List + { + new Embed + { + Title = series.Title, + Description = deleteMessage.DeletedFilesMessage + } + }; + + var payload = CreatePayload("Series Deleted", attachments); + + _proxy.SendPayload(payload, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { var attachments = new List diff --git a/src/NzbDrone.Core/Notifications/Email/Email.cs b/src/NzbDrone.Core/Notifications/Email/Email.cs index 6eee80df5..48f014b05 100644 --- a/src/NzbDrone.Core/Notifications/Email/Email.cs +++ b/src/NzbDrone.Core/Notifications/Email/Email.cs @@ -33,6 +33,20 @@ namespace NzbDrone.Core.Notifications.Email _emailService.SendEmail(Settings, EPISODE_DOWNLOADED_TITLE_BRANDED, body); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + var body = $"{deleteMessage.Message} deleted."; + + _emailService.SendEmail(Settings, EPISODE_DELETED_TITLE_BRANDED, body); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + var body = $"{deleteMessage.Message}"; + + _emailService.SendEmail(Settings, SERIES_DELETED_TITLE_BRANDED, body); + } + public override void OnHealthIssue(HealthCheck.HealthCheck message) { _emailService.SendEmail(Settings, HEALTH_ISSUE_TITLE_BRANDED, message.Message); diff --git a/src/NzbDrone.Core/Notifications/EpisodeDeleteMessage.cs b/src/NzbDrone.Core/Notifications/EpisodeDeleteMessage.cs new file mode 100644 index 000000000..0084a8822 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/EpisodeDeleteMessage.cs @@ -0,0 +1,20 @@ +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Notifications +{ + public class EpisodeDeleteMessage + { + public string Message { get; set; } + public Series Series { get; set; } + public EpisodeFile EpisodeFile { get; set; } + + public DeleteMediaFileReason Reason { get; set; } + + public override string ToString() + { + return Message; + } + } +} + diff --git a/src/NzbDrone.Core/Notifications/Gotify/Gotify.cs b/src/NzbDrone.Core/Notifications/Gotify/Gotify.cs index c251f43ee..146b19696 100644 --- a/src/NzbDrone.Core/Notifications/Gotify/Gotify.cs +++ b/src/NzbDrone.Core/Notifications/Gotify/Gotify.cs @@ -29,6 +29,16 @@ namespace NzbDrone.Core.Notifications.Gotify _proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { _proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings); diff --git a/src/NzbDrone.Core/Notifications/INotification.cs b/src/NzbDrone.Core/Notifications/INotification.cs index b808f75f8..dc7379f4d 100644 --- a/src/NzbDrone.Core/Notifications/INotification.cs +++ b/src/NzbDrone.Core/Notifications/INotification.cs @@ -10,12 +10,16 @@ namespace NzbDrone.Core.Notifications void OnGrab(GrabMessage grabMessage); void OnDownload(DownloadMessage message); void OnRename(Series series); + void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage); + void OnSeriesDelete(SeriesDeleteMessage deleteMessage); void OnHealthIssue(HealthCheck.HealthCheck healthCheck); void ProcessQueue(); bool SupportsOnGrab { get; } bool SupportsOnDownload { get; } bool SupportsOnUpgrade { get; } bool SupportsOnRename { get; } + bool SupportsOnSeriesDelete { get; } + bool SupportsOnEpisodeFileDelete { get; } bool SupportsOnHealthIssue { get; } } } diff --git a/src/NzbDrone.Core/Notifications/Join/Join.cs b/src/NzbDrone.Core/Notifications/Join/Join.cs index d14e8e270..a81c4d123 100644 --- a/src/NzbDrone.Core/Notifications/Join/Join.cs +++ b/src/NzbDrone.Core/Notifications/Join/Join.cs @@ -27,6 +27,15 @@ namespace NzbDrone.Core.Notifications.Join _proxy.SendNotification(EPISODE_DOWNLOADED_TITLE_BRANDED, message.Message, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _proxy.SendNotification(EPISODE_DELETED_TITLE_BRANDED, deleteMessage.Message, Settings); + } + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _proxy.SendNotification(SERIES_DELETED_TITLE_BRANDED, deleteMessage.Message, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck message) { _proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, message.Message, Settings); diff --git a/src/NzbDrone.Core/Notifications/NotificationBase.cs b/src/NzbDrone.Core/Notifications/NotificationBase.cs index 062048e3b..63b7505b9 100644 --- a/src/NzbDrone.Core/Notifications/NotificationBase.cs +++ b/src/NzbDrone.Core/Notifications/NotificationBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentValidation.Results; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Tv; @@ -10,10 +11,14 @@ namespace NzbDrone.Core.Notifications { protected const string EPISODE_GRABBED_TITLE = "Episode Grabbed"; protected const string EPISODE_DOWNLOADED_TITLE = "Episode Downloaded"; + protected const string EPISODE_DELETED_TITLE = "Episode Deleted"; + protected const string SERIES_DELETED_TITLE = "Series Deleted"; protected const string HEALTH_ISSUE_TITLE = "Health Check Failure"; protected const string EPISODE_GRABBED_TITLE_BRANDED = "Sonarr - " + EPISODE_GRABBED_TITLE; protected const string EPISODE_DOWNLOADED_TITLE_BRANDED = "Sonarr - " + EPISODE_DOWNLOADED_TITLE; + protected const string EPISODE_DELETED_TITLE_BRANDED = "Sonarr - " + EPISODE_DELETED_TITLE; + protected const string SERIES_DELETED_TITLE_BRANDED = "Sonarr - " + SERIES_DELETED_TITLE; protected const string HEALTH_ISSUE_TITLE_BRANDED = "Sonarr - " + HEALTH_ISSUE_TITLE; public abstract string Name { get; } @@ -44,6 +49,16 @@ namespace NzbDrone.Core.Notifications } + public virtual void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + + } + + public virtual void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + + } + public virtual void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { @@ -58,6 +73,8 @@ namespace NzbDrone.Core.Notifications public bool SupportsOnRename => HasConcreteImplementation("OnRename"); public bool SupportsOnDownload => HasConcreteImplementation("OnDownload"); public bool SupportsOnUpgrade => SupportsOnDownload; + public bool SupportsOnSeriesDelete => HasConcreteImplementation("OnSeriesDelete"); + public bool SupportsOnEpisodeFileDelete => HasConcreteImplementation("OnEpisodeFileDelete"); public bool SupportsOnHealthIssue => HasConcreteImplementation("OnHealthIssue"); protected TSettings Settings => (TSettings)Definition.Settings; diff --git a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs index 50198d3d7..a444ecee4 100644 --- a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs +++ b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs @@ -8,14 +8,18 @@ namespace NzbDrone.Core.Notifications public bool OnDownload { get; set; } public bool OnUpgrade { get; set; } public bool OnRename { get; set; } + public bool OnSeriesDelete { get; set; } + public bool OnEpisodeFileDelete { get; set; } public bool OnHealthIssue { get; set; } public bool SupportsOnGrab { get; set; } public bool SupportsOnDownload { get; set; } public bool SupportsOnUpgrade { get; set; } public bool SupportsOnRename { get; set; } + public bool SupportsOnSeriesDelete { get; set; } + public bool SupportsOnEpisodeFileDelete { get; set; } public bool SupportsOnHealthIssue { get; set; } public bool IncludeHealthWarnings { get; set; } - public override bool Enable => OnGrab || OnDownload || (OnDownload && OnUpgrade) || OnHealthIssue; + public override bool Enable => OnGrab || OnDownload || (OnDownload && OnUpgrade) || OnSeriesDelete || OnEpisodeFileDelete || OnHealthIssue; } } diff --git a/src/NzbDrone.Core/Notifications/NotificationFactory.cs b/src/NzbDrone.Core/Notifications/NotificationFactory.cs index aab0735b0..882734cad 100644 --- a/src/NzbDrone.Core/Notifications/NotificationFactory.cs +++ b/src/NzbDrone.Core/Notifications/NotificationFactory.cs @@ -13,6 +13,8 @@ namespace NzbDrone.Core.Notifications List OnDownloadEnabled(); List OnUpgradeEnabled(); List OnRenameEnabled(); + List OnSeriesDeleteEnabled(); + List OnEpisodeFileDeleteEnabled(); List OnHealthIssueEnabled(); } @@ -43,6 +45,16 @@ namespace NzbDrone.Core.Notifications return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnRename).ToList(); } + public List OnSeriesDeleteEnabled() + { + return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnSeriesDelete).ToList(); + } + + public List OnEpisodeFileDeleteEnabled() + { + return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnEpisodeFileDelete).ToList(); + } + public List OnHealthIssueEnabled() { return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnHealthIssue).ToList(); @@ -56,6 +68,8 @@ namespace NzbDrone.Core.Notifications definition.SupportsOnDownload = provider.SupportsOnDownload; definition.SupportsOnUpgrade = provider.SupportsOnUpgrade; definition.SupportsOnRename = provider.SupportsOnRename; + definition.SupportsOnSeriesDelete = provider.SupportsOnSeriesDelete; + definition.SupportsOnEpisodeFileDelete = provider.SupportsOnEpisodeFileDelete; definition.SupportsOnHealthIssue = provider.SupportsOnHealthIssue; } } diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs index eb1f811a1..1d4467d06 100644 --- a/src/NzbDrone.Core/Notifications/NotificationService.cs +++ b/src/NzbDrone.Core/Notifications/NotificationService.cs @@ -10,6 +10,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Qualities; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Tv; +using NzbDrone.Core.Tv.Events; namespace NzbDrone.Core.Notifications { @@ -17,7 +18,10 @@ namespace NzbDrone.Core.Notifications : IHandle, IHandle, IHandle, + IHandle, + IHandle, IHandle, + IHandleAsync, IHandleAsync, IHandleAsync, IHandleAsync @@ -189,6 +193,50 @@ namespace NzbDrone.Core.Notifications } } + public void Handle(EpisodeFileDeletedEvent message) + { + var deleteMessage = new EpisodeDeleteMessage(); + deleteMessage.Message = GetMessage(message.EpisodeFile.Series, message.EpisodeFile.Episodes, message.EpisodeFile.Quality); + deleteMessage.Series = message.EpisodeFile.Series; + deleteMessage.EpisodeFile = message.EpisodeFile; + deleteMessage.Reason = message.Reason; + + foreach (var notification in _notificationFactory.OnEpisodeFileDeleteEnabled()) + { + try + { + if (ShouldHandleSeries(notification.Definition, deleteMessage.EpisodeFile.Series)) + { + notification.OnEpisodeFileDelete(deleteMessage); + } + } + catch (Exception ex) + { + _logger.Warn(ex, "Unable to send OnDelete notification to: " + notification.Definition.Name); + } + } + } + + public void Handle(SeriesDeletedEvent message) + { + var deleteMessage = new SeriesDeleteMessage(message.Series,message.DeleteFiles); + + foreach (var notification in _notificationFactory.OnSeriesDeleteEnabled()) + { + try + { + if (ShouldHandleSeries(notification.Definition, deleteMessage.Series)) + { + notification.OnSeriesDelete(deleteMessage); + } + } + catch (Exception ex) + { + _logger.Warn(ex, "Unable to send OnDelete notification to: " + notification.Definition.Name); + } + } + } + public void Handle(HealthCheckFailedEvent message) { foreach (var notification in _notificationFactory.OnHealthIssueEnabled()) @@ -208,6 +256,11 @@ namespace NzbDrone.Core.Notifications } } + public void HandleAsync(DeleteCompletedEvent message) + { + ProcessQueue(); + } + public void HandleAsync(DownloadsProcessedEvent message) { ProcessQueue(); diff --git a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServer.cs b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServer.cs index d1e6e58c7..db1029534 100644 --- a/src/NzbDrone.Core/Notifications/Plex/Server/PlexServer.cs +++ b/src/NzbDrone.Core/Notifications/Plex/Server/PlexServer.cs @@ -48,6 +48,19 @@ namespace NzbDrone.Core.Notifications.Plex.Server UpdateIfEnabled(series); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + UpdateIfEnabled(deleteMessage.Series); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + if (deleteMessage.DeletedFiles) + { + UpdateIfEnabled(deleteMessage.Series); + } + } + private void UpdateIfEnabled(Series series) { if (Settings.UpdateLibrary) diff --git a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs index e3915a824..5eaf036b0 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs @@ -26,6 +26,16 @@ namespace NzbDrone.Core.Notifications.Prowl _prowlProxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings.ApiKey, (ProwlPriority)Settings.Priority); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _prowlProxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings.ApiKey, (ProwlPriority)Settings.Priority); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _prowlProxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings.ApiKey, (ProwlPriority)Settings.Priority); + } + public override void OnHealthIssue(HealthCheck.HealthCheck message) { _prowlProxy.SendNotification(HEALTH_ISSUE_TITLE, message.Message, Settings.ApiKey, (ProwlPriority)Settings.Priority); diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs index d93f38d8c..b97771e48 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs @@ -29,6 +29,15 @@ namespace NzbDrone.Core.Notifications.PushBullet _proxy.SendNotification(EPISODE_DOWNLOADED_TITLE_BRANDED, message.Message, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings); + } + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { _proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, healthCheck.Message, Settings); diff --git a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs index ede513fe9..5f481ad28 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs @@ -26,6 +26,16 @@ namespace NzbDrone.Core.Notifications.Pushover _proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { _proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings); diff --git a/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs b/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs index 6245a3111..0969c8894 100644 --- a/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs +++ b/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs @@ -29,6 +29,15 @@ namespace NzbDrone.Core.Notifications.SendGrid _proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings); + } + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { _proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings); diff --git a/src/NzbDrone.Core/Notifications/SeriesDeleteMessage.cs b/src/NzbDrone.Core/Notifications/SeriesDeleteMessage.cs new file mode 100644 index 000000000..9a02e306b --- /dev/null +++ b/src/NzbDrone.Core/Notifications/SeriesDeleteMessage.cs @@ -0,0 +1,28 @@ +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Notifications +{ + public class SeriesDeleteMessage + { + public string Message { get; set; } + public Series Series { get; set; } + public bool DeletedFiles { get; set; } + public string DeletedFilesMessage { get; set; } + + public override string ToString() + { + return Message; + } + public SeriesDeleteMessage (Series series, bool deleteFiles) + { + Series = series; + DeletedFiles = deleteFiles; + DeletedFilesMessage = DeletedFiles ? + "Series removed and all files were deleted" : + "Series removed, files were not deleted"; + Message = series.Title + " - " + DeletedFilesMessage; + } + } +} + diff --git a/src/NzbDrone.Core/Notifications/Slack/Slack.cs b/src/NzbDrone.Core/Notifications/Slack/Slack.cs index a9c5f8bfa..de35793fb 100644 --- a/src/NzbDrone.Core/Notifications/Slack/Slack.cs +++ b/src/NzbDrone.Core/Notifications/Slack/Slack.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentValidation.Results; using NzbDrone.Common.Extensions; using NzbDrone.Core.Notifications.Slack.Payloads; @@ -70,6 +71,37 @@ namespace NzbDrone.Core.Notifications.Slack _proxy.SendPayload(payload, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + var attachments = new List + { + new Attachment + { + Title = GetTitle(deleteMessage.Series, deleteMessage.EpisodeFile.Episodes), + } + }; + + var payload = CreatePayload("Episode Deleted", attachments); + + _proxy.SendPayload(payload, Settings); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + var attachments = new List + { + new Attachment + { + Title = deleteMessage.Series.Title, + Text = deleteMessage.DeletedFilesMessage + } + }; + + var payload = CreatePayload("Series Deleted", attachments); + + _proxy.SendPayload(payload, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { var attachments = new List @@ -146,5 +178,21 @@ namespace NzbDrone.Core.Notifications.Slack return payload; } + private string GetTitle(Series series, List episodes) + { + if (series.SeriesType == SeriesTypes.Daily) + { + var episode = episodes.First(); + + return $"{series.Title} - {episode.AirDate} - {episode.Title}"; + } + + var episodeNumbers = string.Concat(episodes.Select(e => e.EpisodeNumber) + .Select(i => string.Format("x{0:00}", i))); + + var episodeTitles = string.Join(" + ", episodes.Select(e => e.Title)); + + return $"{series.Title} - {episodes.First().SeasonNumber}{episodeNumbers} - {episodeTitles}"; + } } } diff --git a/src/NzbDrone.Core/Notifications/Synology/SynologyIndexer.cs b/src/NzbDrone.Core/Notifications/Synology/SynologyIndexer.cs index 7006b8d29..a04f77726 100644 --- a/src/NzbDrone.Core/Notifications/Synology/SynologyIndexer.cs +++ b/src/NzbDrone.Core/Notifications/Synology/SynologyIndexer.cs @@ -47,6 +47,25 @@ namespace NzbDrone.Core.Notifications.Synology } } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + if (Settings.UpdateLibrary) + { + var fullPath = Path.Combine(deleteMessage.Series.Path, deleteMessage.EpisodeFile.RelativePath); + _indexerProxy.DeleteFile(fullPath); + } + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + if (deleteMessage.DeletedFiles) + { + if (Settings.UpdateLibrary) + { + _indexerProxy.DeleteFolder(deleteMessage.Series.Path); + } + } + } public override ValidationResult Test() { diff --git a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs index 91e5fd043..3cd5109ee 100644 --- a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs +++ b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs @@ -26,6 +26,16 @@ namespace NzbDrone.Core.Notifications.Telegram _proxy.SendNotification(EPISODE_DOWNLOADED_TITLE, message.Message, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { _proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings); diff --git a/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs b/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs index a357746d1..71c108c2f 100644 --- a/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs +++ b/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs @@ -29,6 +29,16 @@ namespace NzbDrone.Core.Notifications.Twitter _twitterService.SendNotification($"Imported: {message.Message}", Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + _twitterService.SendNotification($"Episode Deleted: {deleteMessage.Message}", Settings); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + _twitterService.SendNotification($"Series Deleted: {deleteMessage.Message}", Settings); + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { _twitterService.SendNotification($"Health Issue: {healthCheck.Message}", Settings); diff --git a/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs b/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs index bc6226be6..18fe1f157 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs @@ -76,6 +76,27 @@ namespace NzbDrone.Core.Notifications.Webhook _proxy.SendWebhook(payload, Settings); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + var payload = new WebhookEpisodeDeletePayload + { + EventType = WebhookEventType.Delete, + Series = new WebhookSeries(deleteMessage.Series), + Episodes = deleteMessage.EpisodeFile.Episodes.Value.ConvertAll(x => new WebhookEpisode(x)) + }; + _proxy.SendWebhook(payload, Settings); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + var payload = new WebhookSeriesDeletePayload + { + EventType = WebhookEventType.Delete, + Series = new WebhookSeries(deleteMessage.Series), + DeletedFiles = deleteMessage.DeletedFiles + }; + } + public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { var payload = new WebhookHealthPayload diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookEpisodeDeletePayload.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookEpisodeDeletePayload.cs new file mode 100644 index 000000000..6dec9ea80 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookEpisodeDeletePayload.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Notifications.Webhook +{ + public class WebhookEpisodeDeletePayload : WebhookPayload + { + public WebhookSeries Series { get; set; } + public List Episodes { get; set; } + } +} + diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookEventType.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookEventType.cs index 0c849834d..60a238b85 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookEventType.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookEventType.cs @@ -12,6 +12,7 @@ namespace NzbDrone.Core.Notifications.Webhook Grab, Download, Rename, + Delete, Health } } diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookSeriesDeletePayload.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookSeriesDeletePayload.cs new file mode 100644 index 000000000..55f84aec2 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookSeriesDeletePayload.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Notifications.Webhook +{ + public class WebhookSeriesDeletePayload : WebhookPayload + { + public WebhookSeries Series { get; set; } + public bool DeletedFiles { get; set; } + } +} + diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs index 9c28840ba..51ae68627 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs @@ -40,6 +40,24 @@ namespace NzbDrone.Core.Notifications.Xbmc { UpdateAndClean(series); } + public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage) + { + const string header = "Sonarr - Deleted"; + + Notify(Settings, header, deleteMessage.Message); + UpdateAndClean(deleteMessage.Series, true); + } + + public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage) + { + if (deleteMessage.DeletedFiles) + { + const string header = "Sonarr - Deleted"; + + Notify(Settings, header, deleteMessage.Message); + UpdateAndClean(deleteMessage.Series, true); + } + } public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) { diff --git a/src/Sonarr.Api.V3/Notifications/NotificationResource.cs b/src/Sonarr.Api.V3/Notifications/NotificationResource.cs index dbc6a59f4..5568201ab 100644 --- a/src/Sonarr.Api.V3/Notifications/NotificationResource.cs +++ b/src/Sonarr.Api.V3/Notifications/NotificationResource.cs @@ -9,11 +9,15 @@ namespace Sonarr.Api.V3.Notifications public bool OnDownload { get; set; } public bool OnUpgrade { get; set; } public bool OnRename { get; set; } + public bool OnSeriesDelete { get; set; } + public bool OnEpisodeFileDelete { get; set; } public bool OnHealthIssue { get; set; } public bool SupportsOnGrab { get; set; } public bool SupportsOnDownload { get; set; } public bool SupportsOnUpgrade { get; set; } public bool SupportsOnRename { get; set; } + public bool SupportsOnSeriesDelete { get; set; } + public bool SupportsOnEpisodeFileDelete { get; set; } public bool SupportsOnHealthIssue { get; set; } public bool IncludeHealthWarnings { get; set; } public string TestCommand { get; set; } @@ -31,11 +35,15 @@ namespace Sonarr.Api.V3.Notifications resource.OnDownload = definition.OnDownload; resource.OnUpgrade = definition.OnUpgrade; resource.OnRename = definition.OnRename; + resource.OnSeriesDelete = definition.OnSeriesDelete; + resource.OnEpisodeFileDelete = definition.OnEpisodeFileDelete; resource.OnHealthIssue = definition.OnHealthIssue; resource.SupportsOnGrab = definition.SupportsOnGrab; resource.SupportsOnDownload = definition.SupportsOnDownload; resource.SupportsOnUpgrade = definition.SupportsOnUpgrade; resource.SupportsOnRename = definition.SupportsOnRename; + resource.SupportsOnSeriesDelete = definition.SupportsOnSeriesDelete; + resource.SupportsOnEpisodeFileDelete = definition.SupportsOnEpisodeFileDelete; resource.SupportsOnHealthIssue = definition.SupportsOnHealthIssue; resource.IncludeHealthWarnings = definition.IncludeHealthWarnings; @@ -52,11 +60,15 @@ namespace Sonarr.Api.V3.Notifications definition.OnDownload = resource.OnDownload; definition.OnUpgrade = resource.OnUpgrade; definition.OnRename = resource.OnRename; + definition.OnSeriesDelete = resource.OnSeriesDelete; + definition.OnEpisodeFileDelete = resource.OnEpisodeFileDelete; definition.OnHealthIssue = resource.OnHealthIssue; definition.SupportsOnGrab = resource.SupportsOnGrab; definition.SupportsOnDownload = resource.SupportsOnDownload; definition.SupportsOnUpgrade = resource.SupportsOnUpgrade; definition.SupportsOnRename = resource.SupportsOnRename; + definition.SupportsOnSeriesDelete = resource.SupportsOnSeriesDelete; + definition.SupportsOnEpisodeFileDelete = resource.SupportsOnEpisodeFileDelete; definition.SupportsOnHealthIssue = resource.SupportsOnHealthIssue; definition.IncludeHealthWarnings = resource.IncludeHealthWarnings;