From fc46018c9a50a1515aca368275696312e207e2fa Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 31 Jul 2014 23:30:30 -0700 Subject: [PATCH] Fixed: Connecting to XBMC when user name and password are configured --- .../Xbmc/GetJsonVersionFixture.cs | 44 ++--- .../Xbmc/Json/ActivePlayersFixture.cs | 120 -------------- .../Xbmc/Json/CheckForErrorFixture.cs | 36 ----- .../Xbmc/Json/GetSeriesPathFixture.cs | 51 +++--- .../Xbmc/Json/UpdateFixture.cs | 92 ++++------- .../NzbDrone.Core.Test.csproj | 2 - .../Notifications/Xbmc/HttpApiProvider.cs | 58 +++---- .../Notifications/Xbmc/IApiProvider.cs | 6 +- .../Notifications/Xbmc/JsonApiProvider.cs | 143 ++--------------- .../Notifications/Xbmc/XbmcJsonApiProxy.cs | 151 ++++++++++++++++++ .../Notifications/Xbmc/XbmcJsonException.cs | 16 ++ .../Notifications/Xbmc/XbmcService.cs | 15 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 2 + 13 files changed, 284 insertions(+), 452 deletions(-) delete mode 100644 src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/ActivePlayersFixture.cs delete mode 100644 src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/CheckForErrorFixture.cs create mode 100644 src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs create mode 100644 src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonException.cs diff --git a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/GetJsonVersionFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/GetJsonVersionFixture.cs index 1f92a27ec..e101942f9 100644 --- a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/GetJsonVersionFixture.cs +++ b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/GetJsonVersionFixture.cs @@ -1,8 +1,7 @@ -using FluentAssertions; -using Moq; +using System; +using FizzWare.NBuilder; +using FluentAssertions; using NUnit.Framework; -using NzbDrone.Common; -using NzbDrone.Common.Http; using NzbDrone.Core.Notifications.Xbmc; using NzbDrone.Core.Notifications.Xbmc.Model; using NzbDrone.Core.Test.Framework; @@ -17,16 +16,15 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc [SetUp] public void Setup() { - _settings = new XbmcSettings - { - Host = "localhost", - Port = 8080, - Username = "xbmc", - Password = "xbmc", - AlwaysUpdate = false, - CleanLibrary = false, - UpdateLibrary = true - }; + _settings = Builder.CreateNew() + .Build(); + } + + private void GivenVersionResponse(String response) + { + Mocker.GetMock() + .Setup(s => s.GetJsonVersion(_settings)) + .Returns(response); } [TestCase(3)] @@ -34,11 +32,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc [TestCase(0)] public void should_get_version_from_major_only(int number) { - var message = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"version\":" + number + "}}"; - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(message); + GivenVersionResponse("{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"version\":" + number + "}}"); Subject.GetJsonVersion(_settings).Should().Be(new XbmcVersion(number)); } @@ -50,11 +44,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc [TestCase(0, 0, 0)] public void should_get_version_from_semantic_version(int major, int minor, int patch) { - var message = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"version\":{\"major\":" + major + ",\"minor\":" + minor + ",\"patch\":" + patch + "}}}"; - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(message); + GivenVersionResponse("{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"version\":{\"major\":" + major + ",\"minor\":" + minor + ",\"patch\":" + patch + "}}}"); Subject.GetJsonVersion(_settings).Should().Be(new XbmcVersion(major, minor, patch)); } @@ -62,11 +52,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc [Test] public void should_get_version_zero_when_an_error_is_received() { - var message = "{\"error\":{\"code\":-32601,\"message\":\"Method not found.\"},\"id\":10,\"jsonrpc\":\"2.0\"}"; - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(message); + GivenVersionResponse("{\"error\":{\"code\":-32601,\"message\":\"Method not found.\"},\"id\":10,\"jsonrpc\":\"2.0\"}"); Subject.GetJsonVersion(_settings).Should().Be(new XbmcVersion(0)); } diff --git a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/ActivePlayersFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/ActivePlayersFixture.cs deleted file mode 100644 index 7047ffe5d..000000000 --- a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/ActivePlayersFixture.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Linq; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common; -using NzbDrone.Common.Http; -using NzbDrone.Core.Notifications.Xbmc; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json -{ - [TestFixture] - public class ActivePlayersFixture : CoreTest - { - private XbmcSettings _settings; - - private void WithNoActivePlayers() - { - Mocker.GetMock() - .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.IsAny())) - .Returns("{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[]}"); - } - - private void WithVideoPlayerActive() - { - Mocker.GetMock() - .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.IsAny())) - .Returns("{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[{\"playerid\":1,\"type\":\"video\"}]}"); - } - - private void WithAudioPlayerActive() - { - Mocker.GetMock() - .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.IsAny())) - .Returns("{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[{\"playerid\":1,\"type\":\"audio\"}]}"); - } - - private void WithPicturePlayerActive() - { - Mocker.GetMock() - .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.IsAny())) - .Returns("{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[{\"playerid\":1,\"type\":\"picture\"}]}"); - } - - private void WithAllPlayersActive() - { - Mocker.GetMock() - .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.IsAny())) - .Returns("{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[{\"playerid\":1,\"type\":\"audio\"},{\"playerid\":2,\"type\":\"picture\"},{\"playerid\":3,\"type\":\"video\"}]}"); - } - - [SetUp] - public void Setup() - { - _settings = new XbmcSettings - { - Host = "localhost", - Port = 8080, - Username = "xbmc", - Password = "xbmc", - AlwaysUpdate = false, - CleanLibrary = false, - UpdateLibrary = true - }; - } - - [Test] - public void _should_be_empty_when_no_active_players() - { - WithNoActivePlayers(); - - Subject.GetActivePlayers(_settings).Should().BeEmpty(); - } - - [Test] - public void should_have_active_video_player() - { - WithVideoPlayerActive(); - - var result = Subject.GetActivePlayers(_settings); - - result.Should().HaveCount(1); - result.First().Type.Should().Be("video"); - } - - [Test] - public void should_have_active_audio_player() - { - WithAudioPlayerActive(); - - var result = Subject.GetActivePlayers(_settings); - - result.Should().HaveCount(1); - result.First().Type.Should().Be("audio"); - } - - [Test] - public void should_have_active_picture_player() - { - WithPicturePlayerActive(); - - var result = Subject.GetActivePlayers(_settings); - - result.Should().HaveCount(1); - result.First().Type.Should().Be("picture"); - } - - [Test] - public void should_have_all_players_active() - { - WithAllPlayersActive(); - - var result = Subject.GetActivePlayers(_settings); - - result.Should().HaveCount(3); - result.Select(a => a.PlayerId).Distinct().Should().HaveCount(3); - result.Select(a => a.Type).Distinct().Should().HaveCount(3); - } - } -} diff --git a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/CheckForErrorFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/CheckForErrorFixture.cs deleted file mode 100644 index b7f0810c7..000000000 --- a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/CheckForErrorFixture.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Notifications.Xbmc; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json -{ - [TestFixture] - public class CheckForErrorFixture : CoreTest - { - [Test] - public void should_be_true_when_the_response_contains_an_error() - { - const string response = "{\"error\":{\"code\":-32601,\"message\":\"Method not found.\"},\"id\":10,\"jsonrpc\":\"2.0\"}"; - - Subject.CheckForError(response).Should().BeTrue(); - } - - [Test] - public void JsonError_true_empty_response() - { - var response = String.Empty; - - Subject.CheckForError(response).Should().BeTrue(); - } - - [Test] - public void JsonError_false() - { - const string response = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"version\":3}}"; - - Subject.CheckForError(response).Should().BeFalse(); - } - } -} diff --git a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/GetSeriesPathFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/GetSeriesPathFixture.cs index 19c846c07..f7ff7e153 100644 --- a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/GetSeriesPathFixture.cs +++ b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/GetSeriesPathFixture.cs @@ -1,9 +1,14 @@ -using FluentAssertions; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using FizzWare.NBuilder; +using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Common.Http; using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Notifications.Xbmc.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -15,42 +20,28 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json private XbmcSettings _settings; private Series _series; private string _response; + private List _xbmcSeries; [SetUp] public void Setup() { - _settings = new XbmcSettings - { - Host = "localhost", - Port = 8080, - Username = "xbmc", - Password = "xbmc", - AlwaysUpdate = false, - CleanLibrary = false, - UpdateLibrary = true - }; + _settings = Builder.CreateNew() + .Build(); - _response = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"limits\":" + - "{\"end\":5,\"start\":0,\"total\":5},\"tvshows\":[{\"file\"" + - ":\"smb://HOMESERVER/TV/7th Heaven/\",\"imdbnumber\":\"73928\"," + - "\"label\":\"7th Heaven\",\"tvshowid\":3},{\"file\":\"smb://HOMESERVER/TV/8 Simple Rules/\"" + - ",\"imdbnumber\":\"78461\",\"label\":\"8 Simple Rules\",\"tvshowid\":4},{\"file\":" + - "\"smb://HOMESERVER/TV/24-7 Penguins-Capitals- Road to the NHL Winter Classic/\",\"imdbnumber\"" + - ":\"213041\",\"label\":\"24/7 Penguins/Capitals: Road to the NHL Winter Classic\",\"tvshowid\":1}," + - "{\"file\":\"smb://HOMESERVER/TV/30 Rock/\",\"imdbnumber\":\"79488\",\"label\":\"30 Rock\",\"tvshowid\":2}" + - ",{\"file\":\"smb://HOMESERVER/TV/90210/\",\"imdbnumber\":\"82716\",\"label\":\"90210\",\"tvshowid\":5}]}}"; + _xbmcSeries = Builder.CreateListOfSize(3) + .Build() + .ToList(); - Mocker.GetMock() - .Setup( - s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.IsAny())) - .Returns(_response); + Mocker.GetMock() + .Setup(s => s.GetSeries(_settings)) + .Returns(_xbmcSeries); } private void WithMatchingTvdbId() { _series = new Series { - TvdbId = 78461, + TvdbId = _xbmcSeries.First().ImdbNumber, Title = "TV Show" }; } @@ -59,8 +50,8 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json { _series = new Series { - TvdbId = 1, - Title = "30 Rock" + TvdbId = 1000, + Title = _xbmcSeries.First().Label }; } @@ -68,7 +59,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json { _series = new Series { - TvdbId = 1, + TvdbId = 1000, Title = "Does not exist" }; } @@ -86,7 +77,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json { WithMatchingTvdbId(); - Subject.GetSeriesPath(_settings, _series).Should().Be("smb://HOMESERVER/TV/8 Simple Rules/"); + Subject.GetSeriesPath(_settings, _series).Should().Be(_xbmcSeries.First().File); } [Test] @@ -94,7 +85,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json { WithMatchingTitle(); - Subject.GetSeriesPath(_settings, _series).Should().Be("smb://HOMESERVER/TV/30 Rock/"); + Subject.GetSeriesPath(_settings, _series).Should().Be(_xbmcSeries.First().File); } } } diff --git a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/UpdateFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/UpdateFixture.cs index e58aa6bc2..9cfb3a457 100644 --- a/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/UpdateFixture.cs +++ b/src/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/UpdateFixture.cs @@ -1,10 +1,11 @@ using System; +using System.Collections.Generic; +using System.Linq; using FizzWare.NBuilder; using Moq; using NUnit.Framework; -using NzbDrone.Common; -using NzbDrone.Common.Http; using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Notifications.Xbmc.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -14,88 +15,53 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json public class UpdateFixture : CoreTest { private XbmcSettings _settings; - const string _expectedJson = "{\"jsonrpc\":\"2.0\",\"method\":\"VideoLibrary.GetTvShows\",\"params\":{\"properties\":[\"file\",\"imdbnumber\"]},\"id\":"; - - private const string _tvshowsResponse = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"limits\":" + - "{\"end\":5,\"start\":0,\"total\":5},\"tvshows\":[{\"file\"" + - ":\"smb://HOMESERVER/TV/7th Heaven/\",\"imdbnumber\":\"73928\"," + - "\"label\":\"7th Heaven\",\"tvshowid\":3},{\"file\":\"smb://HOMESERVER/TV/8 Simple Rules/\"" + - ",\"imdbnumber\":\"78461\",\"label\":\"8 Simple Rules\",\"tvshowid\":4},{\"file\":" + - "\"smb://HOMESERVER/TV/24-7 Penguins-Capitals- Road to the NHL Winter Classic/\",\"imdbnumber\"" + - ":\"213041\",\"label\":\"24/7 Penguins/Capitals: Road to the NHL Winter Classic\",\"tvshowid\":1}," + - "{\"file\":\"smb://HOMESERVER/TV/30 Rock/\",\"imdbnumber\":\"79488\",\"label\":\"30 Rock\",\"tvshowid\":2}" + - ",{\"file\":\"smb://HOMESERVER/TV/90210/\",\"imdbnumber\":\"82716\",\"label\":\"90210\",\"tvshowid\":5}]}}"; + private Series _series; + private List _xbmcSeries; [SetUp] public void Setup() { - _settings = new XbmcSettings - { - Host = "localhost", - Port = 8080, - Username = "xbmc", - Password = "xbmc", - AlwaysUpdate = false, - CleanLibrary = false, - UpdateLibrary = true - }; + _settings = Builder.CreateNew() + .Build(); + + _xbmcSeries = Builder.CreateListOfSize(3) + .Build() + .ToList(); + + Mocker.GetMock() + .Setup(s => s.GetSeries(_settings)) + .Returns(_xbmcSeries); - Mocker.GetMock() - .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, - It.Is(e => e.Replace(" ", "").Replace("\r\n", "").Replace("\t", "").Contains(_expectedJson.Replace(" ", ""))))) - .Returns(_tvshowsResponse); + Mocker.GetMock() + .Setup(s => s.GetActivePlayers(_settings)) + .Returns(new List()); } [Test] public void should_update_using_series_path() { - var fakeSeries = Builder.CreateNew() - .With(s => s.TvdbId = 79488) - .With(s => s.Title = "30 Rock") - .Build(); - - Mocker.GetMock() - .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.Is( - e => e.Replace(" ", "") - .Replace("\r\n", "") - .Replace("\t", "") - .Contains("\"params\":{\"directory\":\"smb://HOMESERVER/TV/30Rock/\"}")))) - .Returns("{\"id\":55,\"jsonrpc\":\"2.0\",\"result\":\"OK\"}"); + var series = Builder.CreateNew() + .With(s => s.TvdbId = _xbmcSeries.First().ImdbNumber) + .Build(); - Subject.Update(_settings, fakeSeries); + Subject.Update(_settings, series); - Mocker.GetMock() - .Verify(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.Is( - e => e.Replace(" ", "") - .Replace("\r\n", "") - .Replace("\t", "") - .Contains("\"params\":{\"directory\":\"smb://HOMESERVER/TV/30Rock/\"}"))), Times.Once()); + Mocker.GetMock() + .Verify(v => v.UpdateLibrary(_settings, It.IsAny()), Times.Once()); } [Test] public void should_update_all_paths_when_series_path_not_found() { var fakeSeries = Builder.CreateNew() - .With(s => s.TvdbId = 1) - .With(s => s.Title = "Not 30 Rock") - .Build(); - - Mocker.GetMock() - .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.Is( - e => !e.Replace(" ", "") - .Replace("\r\n", "") - .Replace("\t", "") - .Contains("\"params\":{\"directory\":\"smb://HOMESERVER/TV/30Rock/\"}")))) - .Returns("{\"id\":55,\"jsonrpc\":\"2.0\",\"result\":\"OK\"}"); + .With(s => s.TvdbId = 1000) + .With(s => s.Title = "Not 30 Rock") + .Build(); Subject.Update(_settings, fakeSeries); - Mocker.GetMock() - .Verify(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.Is( - e => e.Replace(" ", "") - .Replace("\r\n", "") - .Replace("\t", "") - .Contains("\"params\":{\"directory\":\"smb://HOMESERVER/TV/30Rock/\"}"))), Times.Never()); + Mocker.GetMock() + .Verify(v => v.UpdateLibrary(_settings, null), Times.Once()); } } } diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index a60c289e9..9d137cfde 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -208,8 +208,6 @@ - - diff --git a/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs index 048778bed..142aa0def 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs @@ -22,6 +22,11 @@ namespace NzbDrone.Core.Notifications.Xbmc _logger = logger; } + public bool CanHandle(XbmcVersion version) + { + return version < new XbmcVersion(5); + } + public void Notify(XbmcSettings settings, string title, string message) { var notification = String.Format("Notification({0},{1},{2},{3})", title, message, settings.DisplayTime * 1000, "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png"); @@ -55,7 +60,7 @@ namespace NzbDrone.Core.Notifications.Xbmc SendCommand(settings, command); } - public List GetActivePlayers(XbmcSettings settings) + internal List GetActivePlayers(XbmcSettings settings) { try { @@ -75,32 +80,8 @@ namespace NzbDrone.Core.Notifications.Xbmc return new List(); } - - public bool CheckForError(string response) - { - _logger.Debug("Looking for error in response: {0}", response); - - if (String.IsNullOrWhiteSpace(response)) - { - _logger.Debug("Invalid response from XBMC, the response is not valid JSON"); - return true; - } - - var errorIndex = response.IndexOf("Error", StringComparison.InvariantCultureIgnoreCase); - - if (errorIndex > -1) - { - var errorMessage = response.Substring(errorIndex + 6); - errorMessage = errorMessage.Substring(0, errorMessage.IndexOfAny(new char[] { '<', ';' })); - - _logger.Debug("Error found in response: {0}", errorMessage); - return true; - } - - return false; - } - - public string GetSeriesPath(XbmcSettings settings, Series series) + + internal string GetSeriesPath(XbmcSettings settings, Series series) { var query = String.Format( @@ -133,9 +114,28 @@ namespace NzbDrone.Core.Notifications.Xbmc return field.Value; } - public bool CanHandle(XbmcVersion version) + internal bool CheckForError(string response) { - return version < new XbmcVersion(5); + _logger.Debug("Looking for error in response: {0}", response); + + if (String.IsNullOrWhiteSpace(response)) + { + _logger.Debug("Invalid response from XBMC, the response is not valid JSON"); + return true; + } + + var errorIndex = response.IndexOf("Error", StringComparison.InvariantCultureIgnoreCase); + + if (errorIndex > -1) + { + var errorMessage = response.Substring(errorIndex + 6); + errorMessage = errorMessage.Substring(0, errorMessage.IndexOfAny(new char[] { '<', ';' })); + + _logger.Debug("Error found in response: {0}", errorMessage); + return true; + } + + return false; } private void UpdateLibrary(XbmcSettings settings, Series series) diff --git a/src/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs index 28456249a..bf250edc3 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using NzbDrone.Core.Notifications.Xbmc.Model; +using NzbDrone.Core.Notifications.Xbmc.Model; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.Xbmc @@ -9,9 +8,6 @@ namespace NzbDrone.Core.Notifications.Xbmc void Notify(XbmcSettings settings, string title, string message); void Update(XbmcSettings settings, Series series); void Clean(XbmcSettings settings); - List GetActivePlayers(XbmcSettings settings); - bool CheckForError(string response); - string GetSeriesPath(XbmcSettings settings, Series series); bool CanHandle(XbmcVersion version); } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs b/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs index 0f933b570..6b0b7bb01 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs @@ -13,26 +13,23 @@ namespace NzbDrone.Core.Notifications.Xbmc { public class JsonApiProvider : IApiProvider { - private readonly IHttpProvider _httpProvider; + private readonly IXbmcJsonApiProxy _proxy; private readonly Logger _logger; - public JsonApiProvider(IHttpProvider httpProvider, Logger logger) + public JsonApiProvider(IXbmcJsonApiProxy proxy, Logger logger) { - _httpProvider = httpProvider; + _proxy = proxy; _logger = logger; } - public void Notify(XbmcSettings settings, string title, string message) + public bool CanHandle(XbmcVersion version) { - var parameters = new JObject( - new JProperty("title", title), - new JProperty("message", message), - new JProperty("image", "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png"), - new JProperty("displaytime", settings.DisplayTime * 1000)); - - var postJson = BuildJsonRequest("GUI.ShowNotification", parameters); + return version >= new XbmcVersion(5); + } - _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); + public void Notify(XbmcSettings settings, string title, string message) + { + _proxy.Notify(settings, title, message); } public void Update(XbmcSettings settings, Series series) @@ -40,7 +37,7 @@ namespace NzbDrone.Core.Notifications.Xbmc if (!settings.AlwaysUpdate) { _logger.Debug("Determining if there are any active players on XBMC host: {0}", settings.Address); - var activePlayers = GetActivePlayers(settings); + var activePlayers = _proxy.GetActivePlayers(settings); if (activePlayers.Any(a => a.Type.Equals("video"))) { @@ -54,64 +51,17 @@ namespace NzbDrone.Core.Notifications.Xbmc public void Clean(XbmcSettings settings) { - var postJson = BuildJsonRequest("VideoLibrary.Clean"); - - _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); + _proxy.CleanLibrary(settings); } public List GetActivePlayers(XbmcSettings settings) { - try - { - var postJson = new JObject(); - postJson.Add(new JProperty("jsonrpc", "2.0")); - postJson.Add(new JProperty("method", "Player.GetActivePlayers")); - postJson.Add(new JProperty("id", 10)); - - var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); - - if (CheckForError(response)) - return new List(); - - var result = Json.Deserialize(response); - - return result.Result; - } - - catch (Exception ex) - { - _logger.DebugException(ex.Message, ex); - } - - return new List(); - } - - public bool CheckForError(string response) - { - _logger.Debug("Looking for error in response: {0}", response); - - if (String.IsNullOrWhiteSpace(response)) - { - _logger.Debug("Invalid response from XBMC, the response is not valid JSON"); - return true; - } - - if (response.StartsWith("{\"error\"")) - { - var error = Json.Deserialize(response); - var code = error.Error["code"]; - var message = error.Error["message"]; - - _logger.Debug("XBMC Json Error. Code = {0}, Message: {1}", code, message); - return true; - } - - return false; + return _proxy.GetActivePlayers(settings); } - public string GetSeriesPath(XbmcSettings settings, Series series) + public String GetSeriesPath(XbmcSettings settings, Series series) { - var allSeries = GetSeries(settings); + var allSeries = _proxy.GetSeries(settings); if (!allSeries.Any()) { @@ -126,11 +76,6 @@ namespace NzbDrone.Core.Notifications.Xbmc return null; } - public bool CanHandle(XbmcVersion version) - { - return version >= new XbmcVersion(5); - } - private void UpdateLibrary(XbmcSettings settings, Series series) { try @@ -142,27 +87,17 @@ namespace NzbDrone.Core.Notifications.Xbmc if (seriesPath != null) { _logger.Debug("Updating series {0} (Path: {1}) on XBMC host: {2}", series, seriesPath, settings.Address); - - var parameters = new JObject(new JObject(new JProperty("directory", seriesPath))); - postJson = BuildJsonRequest("VideoLibrary.Scan", parameters); } else { _logger.Debug("Series {0} doesn't exist on XBMC host: {1}, Updating Entire Library", series, settings.Address); - - postJson = BuildJsonRequest("VideoLibrary.Scan"); } - var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); + var response = _proxy.UpdateLibrary(settings, null); - if (CheckForError(response)) return; - - _logger.Debug(" from response"); - var result = Json.Deserialize>(response); - - if (!result.Result.Equals("OK", StringComparison.InvariantCultureIgnoreCase)) + if (!response.Equals("OK", StringComparison.InvariantCultureIgnoreCase)) { _logger.Debug("Failed to update library for: {0}", settings.Address); } @@ -173,51 +108,5 @@ namespace NzbDrone.Core.Notifications.Xbmc _logger.DebugException(ex.Message, ex); } } - - private List GetSeries(XbmcSettings settings) - { - try - { - var properties = new JObject { new JProperty("properties", new[] { "file", "imdbnumber" }) }; - var postJson = BuildJsonRequest("VideoLibrary.GetTvShows", properties); - - var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); - - if (CheckForError(response)) - return new List(); - - var result = Json.Deserialize(response); - var shows = result.Result.TvShows; - - return shows; - } - catch (Exception ex) - { - _logger.DebugException(ex.Message, ex); - } - - return new List(); - } - - private JObject BuildJsonRequest(string method) - { - return BuildJsonRequest(method, null); - } - - private JObject BuildJsonRequest(string method, JObject parameters) - { - var postJson = new JObject(); - postJson.Add(new JProperty("jsonrpc", "2.0")); - postJson.Add(new JProperty("method", method)); - - if (parameters != null) - { - postJson.Add(new JProperty("params", parameters)); - } - - postJson.Add(new JProperty("id", 2)); - - return postJson; - } } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs new file mode 100644 index 000000000..a5186bcba --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonApiProxy.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Newtonsoft.Json.Linq; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Notifications.Xbmc.Model; +using NzbDrone.Core.Rest; +using NzbDrone.Core.Tv; +using RestSharp; + +namespace NzbDrone.Core.Notifications.Xbmc +{ + public interface IXbmcJsonApiProxy + { + String GetJsonVersion(XbmcSettings settings); + void Notify(XbmcSettings settings, String title, String message); + String UpdateLibrary(XbmcSettings settings, String path); + void CleanLibrary(XbmcSettings settings); + List GetActivePlayers(XbmcSettings settings); + List GetSeries(XbmcSettings settings); + } + + public class XbmcJsonApiProxy : IXbmcJsonApiProxy + { + private readonly Logger _logger; + + public XbmcJsonApiProxy(Logger logger) + { + _logger = logger; + } + + public String GetJsonVersion(XbmcSettings settings) + { + var request = new RestRequest(); + return ProcessRequest(request, settings, "JSONRPC.Version"); + } + + public void Notify(XbmcSettings settings, String title, String message) + { + var request = new RestRequest(); + + var parameters = new Dictionary(); + parameters.Add("title", title); + parameters.Add("message", message); + parameters.Add("image", "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png"); + parameters.Add("displaytime", settings.DisplayTime * 1000); + + ProcessRequest(request, settings, "GUI.ShowNotification", parameters); + } + + public String UpdateLibrary(XbmcSettings settings, String path) + { + var request = new RestRequest(); + var parameters = new Dictionary(); + parameters.Add("directory", path ); + + if (path.IsNullOrWhiteSpace()) + { + parameters = null; + } + + var response = ProcessRequest(request, settings, "VideoLibrary.Scan", parameters); + + return Json.Deserialize>(response).Result; + } + + public void CleanLibrary(XbmcSettings settings) + { + var request = new RestRequest(); + + ProcessRequest(request, settings, "VideoLibrary.Clean"); + } + + public List GetActivePlayers(XbmcSettings settings) + { + var request = new RestRequest(); + + var response = ProcessRequest(request, settings, "Player.GetActivePlayers"); + + return Json.Deserialize(response).Result; + } + + public List GetSeries(XbmcSettings settings) + { + var request = new RestRequest(); + var parameters = new Dictionary(); + parameters.Add("properties", new[] { "file", "imdbnumber" }); + + var response = ProcessRequest(request, settings, "VideoLibrary.GetTvShows", parameters); + + return Json.Deserialize(response).Result.TvShows; + } + + private String ProcessRequest(IRestRequest request, XbmcSettings settings, String method, Dictionary parameters = null) + { + var client = BuildClient(settings); + + request.Method = Method.POST; + request.RequestFormat = DataFormat.Json; + request.JsonSerializer = new JsonNetSerializer(); + request.AddBody(new { jsonrpc = "2.0", method = method, id = 10, @params = parameters }); + + var response = client.ExecuteAndValidate(request); + _logger.Trace("Response: {0}", response.Content); + + CheckForError(response); + + return response.Content; + } + + private IRestClient BuildClient(XbmcSettings settings) + { + var url = string.Format(@"http://{0}/jsonrpc", settings.Address); + + _logger.Debug("Url: " + url); + + var client = RestClientFactory.BuildClient(url); + + if (!settings.Username.IsNullOrWhiteSpace()) + { + client.Authenticator = new HttpBasicAuthenticator(settings.Username, settings.Password); + } + + return client; + } + + private void CheckForError(IRestResponse response) + { + _logger.Debug("Looking for error in response: {0}", response); + + if (String.IsNullOrWhiteSpace(response.Content)) + { + throw new XbmcJsonException("Invalid response from XBMC, the response is not valid JSON"); + } + + if (response.Content.StartsWith("{\"error\"")) + { + var error = Json.Deserialize(response.Content); + var code = error.Error["code"]; + var message = error.Error["message"]; + + var errorMessage = String.Format("XBMC Json Error. Code = {0}, Message: {1}", code, message); + throw new XbmcJsonException(errorMessage); + } + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonException.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonException.cs new file mode 100644 index 000000000..95e5bbcf4 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcJsonException.cs @@ -0,0 +1,16 @@ +using System; + +namespace NzbDrone.Core.Notifications.Xbmc +{ + public class XbmcJsonException : Exception + { + public XbmcJsonException() + { + } + + public XbmcJsonException(string message) + : base(message) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs index de38e5fed..4d2e3d5da 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs @@ -4,8 +4,6 @@ using System.Linq; using FluentValidation.Results; using Newtonsoft.Json.Linq; using NLog; -using NzbDrone.Common.Http; -using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Serializer; using NzbDrone.Core.Notifications.Xbmc.Model; using NzbDrone.Core.Tv; @@ -23,13 +21,13 @@ namespace NzbDrone.Core.Notifications.Xbmc public class XbmcService : IXbmcService { - private readonly IHttpProvider _httpProvider; + private readonly IXbmcJsonApiProxy _proxy; private readonly IEnumerable _apiProviders; private readonly Logger _logger; - public XbmcService(IHttpProvider httpProvider, IEnumerable apiProviders, Logger logger) + public XbmcService(IXbmcJsonApiProxy proxy, IEnumerable apiProviders, Logger logger) { - _httpProvider = httpProvider; + _proxy = proxy; _apiProviders = apiProviders; _logger = logger; } @@ -56,12 +54,7 @@ namespace NzbDrone.Core.Notifications.Xbmc { try { - var postJson = new JObject(); - postJson.Add(new JProperty("jsonrpc", "2.0")); - postJson.Add(new JProperty("method", "JSONRPC.Version")); - postJson.Add(new JProperty("id", 1)); - - var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); + var response = _proxy.GetJsonVersion(settings); _logger.Debug("Getting version from response: " + response); var result = Json.Deserialize>(response); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 0c7652dea..6ea6013bd 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -603,6 +603,7 @@ + @@ -620,6 +621,7 @@ Code + Code