From daafb67fa7e3cc3e320037fc51a61485849b38ba Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 3 Aug 2014 00:26:55 -0700 Subject: [PATCH] New: MediaBrowser notifications --- src/NzbDrone.Common/Http/HttpClient.cs | 15 ++++ .../MediaBrowser/MediaBrowser.cs | 65 +++++++++++++++++ .../MediaBrowser/MediaBrowserProxy.cs | 71 +++++++++++++++++++ .../MediaBrowser/MediaBrowserService.cs | 62 ++++++++++++++++ .../MediaBrowser/MediaBrowserSettings.cs | 64 +++++++++++++++++ .../Notifications/Xbmc/XbmcJsonApiProxy.cs | 5 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 4 ++ src/NzbDrone.Core/Rest/RestSharpExtensions.cs | 4 ++ 8 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowser.cs create mode 100644 src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs create mode 100644 src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs create mode 100644 src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs diff --git a/src/NzbDrone.Common/Http/HttpClient.cs b/src/NzbDrone.Common/Http/HttpClient.cs index 0af9c3bbd..464d2ff53 100644 --- a/src/NzbDrone.Common/Http/HttpClient.cs +++ b/src/NzbDrone.Common/Http/HttpClient.cs @@ -15,6 +15,8 @@ namespace NzbDrone.Common.Http HttpResponse Get(HttpRequest request); HttpResponse Get(HttpRequest request) where T : new(); HttpResponse Head(HttpRequest request); + HttpResponse Post(HttpRequest request); + HttpResponse Post(HttpRequest request) where T : new(); } public class HttpClient : IHttpClient @@ -43,6 +45,7 @@ namespace NzbDrone.Common.Http webRequest.UserAgent = UserAgentBuilder.UserAgent; webRequest.KeepAlive = false; webRequest.AllowAutoRedirect = request.AllowAutoRedirect; + webRequest.ContentLength = 0; if (!RuntimeInfoBase.IsProduction) { @@ -164,6 +167,18 @@ namespace NzbDrone.Common.Http return Execute(request); } + public HttpResponse Post(HttpRequest request) + { + request.Method = HttpMethod.POST; + return Execute(request); + } + + public HttpResponse Post(HttpRequest request) where T : new() + { + var response = Post(request); + return new HttpResponse(response); + } + protected virtual void AddRequestHeaders(HttpWebRequest webRequest, HttpHeader headers) { foreach (var header in headers) diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowser.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowser.cs new file mode 100644 index 000000000..a63c2a8ba --- /dev/null +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowser.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Notifications.MediaBrowser +{ + public class MediaBrowser : NotificationBase + { + private readonly IMediaBrowserService _mediaBrowserService; + + public MediaBrowser(IMediaBrowserService mediaBrowserService) + { + _mediaBrowserService = mediaBrowserService; + } + + public override string Link + { + get { return "http://mediabrowser.tv/"; } + } + + public override void OnGrab(String message) + { + const string title = "Sonarr - Grabbed"; + + if (Settings.Notify) + { + _mediaBrowserService.Notify(Settings, title, message); + } + } + + public override void OnDownload(DownloadMessage message) + { + const string title = "Sonarr - Downloaded"; + + if (Settings.Notify) + { + _mediaBrowserService.Notify(Settings, title, message.Message); + } + + if (Settings.UpdateLibrary) + { + _mediaBrowserService.Update(Settings, message.Series); + } + } + + public override void AfterRename(Series series) + { + if (Settings.UpdateLibrary) + { + _mediaBrowserService.Update(Settings, series); + } + } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_mediaBrowserService.Test(Settings)); + + return new ValidationResult(failures); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs new file mode 100644 index 000000000..1b7553e7c --- /dev/null +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserProxy.cs @@ -0,0 +1,71 @@ +using System; +using Newtonsoft.Json; +using NLog; +using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; + +namespace NzbDrone.Core.Notifications.MediaBrowser +{ + public class MediaBrowserProxy + { + private readonly IHttpClient _httpClient; + private readonly Logger _logger; + + public MediaBrowserProxy(IHttpClient httpClient, Logger logger) + { + _httpClient = httpClient; + _logger = logger; + } + + public void Notify(MediaBrowserSettings settings, string title, string message) + { + var path = "/Notifications/Admin"; + var request = BuildRequest(path, settings); + + request.Body = new + { + Name = title, + Description = message, + ImageUrl = "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png" + }.ToJson(); + + request.Headers.ContentType = "application/json"; + + ProcessRequest(request, settings); + } + + public void Update(MediaBrowserSettings settings, Int32 tvdbId) + { + var path = String.Format("/Library/Series/Updated?tvdbid={0}", tvdbId); + var request = BuildRequest(path, settings); + + ProcessRequest(request, settings); + } + + private String ProcessRequest(HttpRequest request, MediaBrowserSettings settings) + { + request.Headers.Add("X-MediaBrowser-Token", settings.ApiKey); + + var response = _httpClient.Post(request); + _logger.Trace("Response: {0}", response.Content); + + CheckForError(response); + + return response.Content; + } + + private HttpRequest BuildRequest(string path, MediaBrowserSettings settings) + { + var url = String.Format(@"http://{0}/mediabrowser", settings.Address); + + return new HttpRequestBuilder(url).Build(path); + } + + private void CheckForError(HttpResponse response) + { + _logger.Debug("Looking for error in response: {0}", response); + + //TODO: actually check for the error + } + } +} diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs new file mode 100644 index 000000000..905ba70ee --- /dev/null +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserService.cs @@ -0,0 +1,62 @@ +using System; +using System.Net; +using FluentValidation.Results; +using NLog; +using NzbDrone.Core.Rest; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Notifications.MediaBrowser +{ + public interface IMediaBrowserService + { + void Notify(MediaBrowserSettings settings, String title, String message); + void Update(MediaBrowserSettings settings, Series series); + ValidationFailure Test(MediaBrowserSettings settings); + } + + public class MediaBrowserService : IMediaBrowserService + { + private readonly MediaBrowserProxy _proxy; + private readonly Logger _logger; + + public MediaBrowserService(MediaBrowserProxy proxy, Logger logger) + { + _proxy = proxy; + _logger = logger; + } + + public void Notify(MediaBrowserSettings settings, String title, String message) + { + _proxy.Notify(settings, title, message); + } + + public void Update(MediaBrowserSettings settings, Series series) + { + _proxy.Update(settings, series.TvdbId); + } + + public ValidationFailure Test(MediaBrowserSettings settings) + { + try + { + _logger.Debug("Testing connection to MediaBrowser: {0}", settings.Address); + + Notify(settings, "Test from Sonarr", "Success! MediaBrowser has been successfully configured!"); + } + catch (RestException ex) + { + if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) + { + return new ValidationFailure("ApiKey", "API Key is incorrect"); + } + } + catch (Exception ex) + { + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("Host", "Unable to send test message: " + ex.Message); + } + + return null; + } + } +} diff --git a/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs new file mode 100644 index 000000000..323b184de --- /dev/null +++ b/src/NzbDrone.Core/Notifications/MediaBrowser/MediaBrowserSettings.cs @@ -0,0 +1,64 @@ +using System; +using FluentValidation; +using FluentValidation.Results; +using Newtonsoft.Json; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Notifications.MediaBrowser +{ + public class MediaBrowserSettingsValidator : AbstractValidator + { + public MediaBrowserSettingsValidator() + { + RuleFor(c => c.Host).NotEmpty(); + RuleFor(c => c.ApiKey).NotEmpty(); + } + } + + public class MediaBrowserSettings : IProviderConfig + { + private static readonly MediaBrowserSettingsValidator Validator = new MediaBrowserSettingsValidator(); + + public MediaBrowserSettings() + { + Port = 8096; + } + + [FieldDefinition(0, Label = "Host")] + public String Host { get; set; } + + [FieldDefinition(1, Label = "Port")] + public Int32 Port { get; set; } + + [FieldDefinition(2, Label = "API Key")] + public String ApiKey { get; set; } + + [FieldDefinition(3, Label = "Send Notifications", HelpText = "Have MediaBrowser send notfications to configured providers", Type = FieldType.Checkbox)] + public Boolean Notify { get; set; } + + [FieldDefinition(4, Label = "Update Library", HelpText = "Update Library on Download & Rename?", Type = FieldType.Checkbox)] + public Boolean UpdateLibrary { get; set; } + + [JsonIgnore] + public String Address { get { return String.Format("{0}:{1}", Host, Port); } } + + public bool IsValid + { + get + { + return !string.IsNullOrWhiteSpace(Host) && Port > 0; + } + } + + public ValidationResult Validate() + { + return Validator.Validate(this); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs index ae0c020ee..c45875c35 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs @@ -109,10 +109,7 @@ namespace NzbDrone.Core.Notifications.Xbmc private IRestClient BuildClient(XbmcSettings settings) { - var url = string.Format(@"http://{0}/jsonrpc", settings.Address); - - _logger.Debug("Url: " + url); - + var url = String.Format(@"http://{0}/jsonrpc", settings.Address); var client = RestClientFactory.BuildClient(url); if (!settings.Username.IsNullOrWhiteSpace()) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index e059204dc..b3047621c 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -657,6 +657,10 @@ + + + + Code diff --git a/src/NzbDrone.Core/Rest/RestSharpExtensions.cs b/src/NzbDrone.Core/Rest/RestSharpExtensions.cs index f08552559..37958317f 100644 --- a/src/NzbDrone.Core/Rest/RestSharpExtensions.cs +++ b/src/NzbDrone.Core/Rest/RestSharpExtensions.cs @@ -39,6 +39,10 @@ namespace NzbDrone.Core.Rest { return response; } + case HttpStatusCode.NoContent: + { + return response; + } default: { Logger.Warn("[{0}] [{1}] Failed. [{2}]", response.Request.Method, response.ResponseUri.ToString(), response.StatusCode);