diff --git a/src/NzbDrone.Core/HealthCheck/CheckOnAttribute.cs b/src/NzbDrone.Core/HealthCheck/CheckOnAttribute.cs index ee427cb38..7e7b9d259 100644 --- a/src/NzbDrone.Core/HealthCheck/CheckOnAttribute.cs +++ b/src/NzbDrone.Core/HealthCheck/CheckOnAttribute.cs @@ -1,5 +1,4 @@ -using System; -using NzbDrone.Common.Messaging; +using System; namespace NzbDrone.Core.HealthCheck { @@ -7,10 +6,19 @@ namespace NzbDrone.Core.HealthCheck public class CheckOnAttribute: Attribute { public Type EventType { get; set; } + public CheckOnCondition Condition { get; set; } - public CheckOnAttribute(Type eventType) + public CheckOnAttribute(Type eventType, CheckOnCondition condition = CheckOnCondition.Always) { EventType = eventType; + Condition = condition; } } + + public enum CheckOnCondition + { + Always, + FailedOnly, + SuccessfulOnly + } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs index ae3064425..f5605f661 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs @@ -1,5 +1,6 @@ -using System.Linq; +using System.Linq; using NzbDrone.Common.Disk; +using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Tv; using NzbDrone.Core.Tv.Events; @@ -7,6 +8,8 @@ namespace NzbDrone.Core.HealthCheck.Checks { [CheckOn(typeof(SeriesDeletedEvent))] [CheckOn(typeof(SeriesMovedEvent))] + [CheckOn(typeof(EpisodeImportedEvent), CheckOnCondition.FailedOnly)] + [CheckOn(typeof(EpisodeImportFailedEvent), CheckOnCondition.SuccessfulOnly)] public class RootFolderCheck : HealthCheckBase { private readonly ISeriesService _seriesService; diff --git a/src/NzbDrone.Core/HealthCheck/EventDrivenHealthCheck.cs b/src/NzbDrone.Core/HealthCheck/EventDrivenHealthCheck.cs new file mode 100644 index 000000000..0b55c1ff2 --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/EventDrivenHealthCheck.cs @@ -0,0 +1,14 @@ +namespace NzbDrone.Core.HealthCheck +{ + public class EventDrivenHealthCheck + { + public IProvideHealthCheck HealthCheck { get; set; } + public CheckOnCondition Condition { get; set; } + + public EventDrivenHealthCheck(IProvideHealthCheck healthCheck, CheckOnCondition condition) + { + HealthCheck = healthCheck; + Condition = condition; + } + } +} diff --git a/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs b/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs index 3ddd11d8a..f9c35d515 100644 --- a/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs +++ b/src/NzbDrone.Core/HealthCheck/HealthCheckService.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Core.HealthCheck private readonly IProvideHealthCheck[] _healthChecks; private readonly IProvideHealthCheck[] _startupHealthChecks; private readonly IProvideHealthCheck[] _scheduledHealthChecks; - private readonly Dictionary _eventDrivenHealthChecks; + private readonly Dictionary _eventDrivenHealthChecks; private readonly IEventAggregator _eventAggregator; private readonly ICacheManager _cacheManager; private readonly Logger _logger; @@ -58,10 +58,10 @@ namespace NzbDrone.Core.HealthCheck return _healthCheckResults.Values.ToList(); } - private Dictionary GetEventDrivenHealthChecks() + private Dictionary GetEventDrivenHealthChecks() { return _healthChecks - .SelectMany(h => h.GetType().GetAttributes().Select(a => Tuple.Create(a.EventType, h))) + .SelectMany(h => h.GetType().GetAttributes().Select(a => Tuple.Create(a.EventType, new EventDrivenHealthCheck(h, a.Condition)))) .GroupBy(t => t.Item1, t => t.Item2) .ToDictionary(g => g.Key, g => g.ToArray()); } @@ -111,15 +111,43 @@ namespace NzbDrone.Core.HealthCheck return; } - IProvideHealthCheck[] checks; + EventDrivenHealthCheck[] checks; if (!_eventDrivenHealthChecks.TryGetValue(message.GetType(), out checks)) { return; } + var filteredChecks = new List(); + var healthCheckResults = _healthCheckResults.Values.ToList(); + + foreach (var eventDrivenHealthCheck in checks) + { + if (eventDrivenHealthCheck.Condition == CheckOnCondition.Always) + { + filteredChecks.Add(eventDrivenHealthCheck.HealthCheck); + continue; + } + + var healthCheckType = eventDrivenHealthCheck.HealthCheck.GetType(); + + if (eventDrivenHealthCheck.Condition == CheckOnCondition.FailedOnly && + healthCheckResults.Any(r => r.Source == healthCheckType)) + { + filteredChecks.Add(eventDrivenHealthCheck.HealthCheck); + continue; + } + + if (eventDrivenHealthCheck.Condition == CheckOnCondition.SuccessfulOnly && + healthCheckResults.None(r => r.Source == healthCheckType)) + { + filteredChecks.Add(eventDrivenHealthCheck.HealthCheck); + } + } + + // TODO: Add debounce - PerformHealthCheck(checks); + PerformHealthCheck(filteredChecks.ToArray()); } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs index 648becce6..5ffed5567 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFileMovingService.cs @@ -12,7 +12,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; -using NzbDrone.Common; +using NzbDrone.Core.MediaFiles.EpisodeImport; namespace NzbDrone.Core.MediaFiles { @@ -158,7 +158,7 @@ namespace NzbDrone.Core.MediaFiles if (!_diskProvider.FolderExists(rootFolder)) { - throw new EpisodeImport.RootFolderNotFoundException(string.Format("Root folder '{0}' was not found.", rootFolder)); + throw new RootFolderNotFoundException(string.Format("Root folder '{0}' was not found.", rootFolder)); } var changed = false; diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 71ad64609..7c281c65a 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -125,6 +125,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport catch (RootFolderNotFoundException e) { _logger.Warn(e, "Couldn't import episode " + localEpisode); + _eventAggregator.PublishEvent(new EpisodeImportFailedEvent(e, localEpisode, newDownload, downloadClientItem)); + importResults.Add(new ImportResult(importDecision, "Failed to import episode, Root folder missing.")); } catch (DestinationAlreadyExistsException e) diff --git a/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportFailedEvent.cs b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportFailedEvent.cs new file mode 100644 index 000000000..7a85ff891 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Events/EpisodeImportFailedEvent.cs @@ -0,0 +1,29 @@ +using System; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.Events +{ + public class EpisodeImportFailedEvent : IEvent + { + public Exception Exception { get; set; } + public LocalEpisode EpisodeInfo { get; } + public bool NewDownload { get; } + public string DownloadClient { get; } + public string DownloadId { get; } + + public EpisodeImportFailedEvent(Exception exception, LocalEpisode episodeInfo, bool newDownload, DownloadClientItem downloadClientItem) + { + Exception = exception; + EpisodeInfo = episodeInfo; + NewDownload = newDownload; + + if (downloadClientItem != null) + { + DownloadClient = downloadClientItem.DownloadClient; + DownloadId = downloadClientItem.DownloadId; + } + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index ebad14f1e..41f998e50 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -587,6 +587,7 @@ +