From b99e62c5ba5aaf886b89a62afbef0c20b5c034ba Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 11 Jun 2013 23:45:25 -0700 Subject: [PATCH] Xbmc Refactored --- NzbDrone.Common/NzbDrone.Common.csproj | 3 +- ...{StringExtention.cs => StringExtension.cs} | 0 NzbDrone.Common/UdpProvider.cs | 191 ------ .../GrowlProviderTest.cs | 0 .../NotificationServiceFixture.cs | 6 +- .../PlexProviderTest.cs | 0 .../ProwlProviderTest.cs | 0 .../Xbmc/GetJsonVersionFixture.cs | 80 +++ .../Xbmc/Http/ActivePlayersFixture.cs | 74 ++ .../Xbmc/Http/CheckForErrorFixture.cs | 41 ++ .../Xbmc/Http/GetSeriesPathFixture.cs | 99 +++ .../Xbmc/Http/UpdateFixture.cs | 81 +++ .../Xbmc/HttpApiProviderFixture.cs | 34 + .../Xbmc/Json/ActivePlayersFixture.cs | 122 ++++ .../Xbmc/Json/CheckForErrorFixture.cs | 39 ++ .../Xbmc/Json/GetSeriesPathFixture.cs | 103 +++ .../Xbmc/Json/UpdateFixture.cs | 104 +++ .../Xbmc/JsonApiProviderFixture.cs | 32 + .../Xbmc/XbmcServiceFixture.cs | 40 ++ NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 20 +- .../ProviderTests/EventClientProviderTest.cs | 98 --- .../ProviderTests/XbmcProviderTest.cs | 637 ------------------ NzbDrone.Core/Model/Xbmc/ActionType.cs | 12 - .../Model/Xbmc/ActivePlayersEdenResult.cs | 6 + NzbDrone.Core/Model/Xbmc/IconType.cs | 14 - .../Notifications/Xbmc/EventClientProvider.cs | 64 -- .../Notifications/Xbmc/HttpApiProvider.cs | 190 ++++++ .../Notifications/Xbmc/IApiProvider.cs | 20 + .../Xbmc/InvalidXbmcVersionException.cs | 18 + .../Notifications/Xbmc/JsonApiProvider.cs | 225 +++++++ .../Notifications/Xbmc/TestXbmcCommand.cs | 12 + NzbDrone.Core/Notifications/Xbmc/Xbmc.cs | 4 +- .../Notifications/Xbmc/XbmcProvider.cs | 455 ------------- .../Notifications/Xbmc/XbmcService.cs | 118 ++++ .../Notifications/Xbmc/XbmcSettings.cs | 4 + NzbDrone.Core/NzbDrone.Core.csproj | 12 +- UI/.idea/runConfigurations/Debug___Chrome.xml | 32 +- .../runConfigurations/Debug___Firefox.xml | 32 +- 38 files changed, 1501 insertions(+), 1521 deletions(-) rename NzbDrone.Common/{StringExtention.cs => StringExtension.cs} (100%) delete mode 100644 NzbDrone.Common/UdpProvider.cs rename NzbDrone.Core.Test/{ProviderTests => NotificationTests}/GrowlProviderTest.cs (100%) rename NzbDrone.Core.Test/{ProviderTests => NotificationTests}/PlexProviderTest.cs (100%) rename NzbDrone.Core.Test/{ProviderTests => NotificationTests}/ProwlProviderTest.cs (100%) create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/GetJsonVersionFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/Http/ActivePlayersFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/Http/CheckForErrorFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/Http/GetSeriesPathFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/Http/UpdateFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/HttpApiProviderFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/Json/ActivePlayersFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/Json/CheckForErrorFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/Json/GetSeriesPathFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/Json/UpdateFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/JsonApiProviderFixture.cs create mode 100644 NzbDrone.Core.Test/NotificationTests/Xbmc/XbmcServiceFixture.cs delete mode 100644 NzbDrone.Core.Test/ProviderTests/EventClientProviderTest.cs delete mode 100644 NzbDrone.Core.Test/ProviderTests/XbmcProviderTest.cs delete mode 100644 NzbDrone.Core/Model/Xbmc/ActionType.cs delete mode 100644 NzbDrone.Core/Model/Xbmc/IconType.cs delete mode 100644 NzbDrone.Core/Notifications/Xbmc/EventClientProvider.cs create mode 100644 NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs create mode 100644 NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs create mode 100644 NzbDrone.Core/Notifications/Xbmc/InvalidXbmcVersionException.cs create mode 100644 NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs create mode 100644 NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs delete mode 100644 NzbDrone.Core/Notifications/Xbmc/XbmcProvider.cs create mode 100644 NzbDrone.Core/Notifications/Xbmc/XbmcService.cs diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index 62a0028a6..e1bb25519 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -138,7 +138,7 @@ - + @@ -155,7 +155,6 @@ - diff --git a/NzbDrone.Common/StringExtention.cs b/NzbDrone.Common/StringExtension.cs similarity index 100% rename from NzbDrone.Common/StringExtention.cs rename to NzbDrone.Common/StringExtension.cs diff --git a/NzbDrone.Common/UdpProvider.cs b/NzbDrone.Common/UdpProvider.cs deleted file mode 100644 index 94956c125..000000000 --- a/NzbDrone.Common/UdpProvider.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System.Linq; -using System; -using System.Net; -using System.Net.Sockets; -using NLog; - -namespace NzbDrone.Common -{ - public class UdpProvider - { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - public UdpProvider() - { - - } - - private const int StandardPort = 9777; - private const int MaxPacketSize = 1024; - private const int HeaderSize = 32; - private const int MaxPayloadSize = MaxPacketSize - HeaderSize; - private const byte MajorVersion = 2; - private const byte MinorVersion = 0; - - public enum PacketType - { - Helo = 0x01, - Bye = 0x02, - Button = 0x03, - Mouse = 0x04, - Ping = 0x05, - Broadcast = 0x06, //Currently not implemented - Notification = 0x07, - Blob = 0x08, - Log = 0x09, - Action = 0x0A, - Debug = 0xFF //Currently not implemented - } - - private byte[] Header(PacketType packetType, int numberOfPackets, int currentPacket, int payloadSize, uint uniqueToken) - { - byte[] header = new byte[HeaderSize]; - - header[0] = (byte)'X'; - header[1] = (byte)'B'; - header[2] = (byte)'M'; - header[3] = (byte)'C'; - - header[4] = MajorVersion; - header[5] = MinorVersion; - - if (currentPacket == 1) - { - header[6] = (byte)(((ushort)packetType & 0xff00) >> 8); - header[7] = (byte)((ushort)packetType & 0x00ff); - } - else - { - header[6] = ((ushort)PacketType.Blob & 0xff00) >> 8; - header[7] = (ushort)PacketType.Blob & 0x00ff; - } - - header[8] = (byte)((currentPacket & 0xff000000) >> 24); - header[9] = (byte)((currentPacket & 0x00ff0000) >> 16); - header[10] = (byte)((currentPacket & 0x0000ff00) >> 8); - header[11] = (byte)(currentPacket & 0x000000ff); - - header[12] = (byte)((numberOfPackets & 0xff000000) >> 24); - header[13] = (byte)((numberOfPackets & 0x00ff0000) >> 16); - header[14] = (byte)((numberOfPackets & 0x0000ff00) >> 8); - header[15] = (byte)(numberOfPackets & 0x000000ff); - - header[16] = (byte)((payloadSize & 0xff00) >> 8); - header[17] = (byte)(payloadSize & 0x00ff); - - header[18] = (byte)((uniqueToken & 0xff000000) >> 24); - header[19] = (byte)((uniqueToken & 0x00ff0000) >> 16); - header[20] = (byte)((uniqueToken & 0x0000ff00) >> 8); - header[21] = (byte)(uniqueToken & 0x000000ff); - - return header; - - } - - public virtual bool Send(string address, PacketType packetType, byte[] payload) - { - var uniqueToken = (uint)DateTime.Now.TimeOfDay.Milliseconds; - - var socket = Connect(address, StandardPort); - - if (socket == null || !socket.Connected) - { - return false; - } - - try - { - bool successfull = true; - int packetCount = (payload.Length / MaxPayloadSize) + 1; - int bytesToSend = 0; - int bytesSent = 0; - int bytesLeft = payload.Length; - - for (int Package = 1; Package <= packetCount; Package++) - { - - if (bytesLeft > MaxPayloadSize) - { - bytesToSend = MaxPayloadSize; - bytesLeft -= bytesToSend; - } - else - { - bytesToSend = bytesLeft; - bytesLeft = 0; - } - - byte[] header = Header(packetType, packetCount, Package, bytesToSend, uniqueToken); - byte[] packet = new byte[MaxPacketSize]; - - Array.Copy(header, 0, packet, 0, header.Length); - Array.Copy(payload, bytesSent, packet, header.Length, bytesToSend); - - int sendSize = socket.Send(packet, header.Length + bytesToSend, SocketFlags.None); - - if (sendSize != (header.Length + bytesToSend)) - { - successfull = false; - break; - } - - bytesSent += bytesToSend; - } - Disconnect(socket); - return successfull; - } - - catch - { - Disconnect(socket); - return false; - } - } - - private Socket Connect(string address, int port) - { - try - { - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - - IPAddress ip; - if (!IPAddress.TryParse(address, out ip)) - { - IPHostEntry ipHostEntry = Dns.GetHostEntry(address); - foreach (IPAddress ipAddress in ipHostEntry.AddressList) - { - if (ipAddress.AddressFamily == AddressFamily.InterNetwork) - { - ip = ipAddress; - break; - } - } - } - - socket.Connect(new IPEndPoint(ip, port)); - return socket; - } - - catch (Exception exc) - { - Logger.TraceException(exc.Message, exc); - return null; - } - } - - private void Disconnect(Socket socket) - { - try - { - if (socket != null) - { - socket.Shutdown(SocketShutdown.Both); - socket.Close(); - } - } - catch - { - } - } - } -} diff --git a/NzbDrone.Core.Test/ProviderTests/GrowlProviderTest.cs b/NzbDrone.Core.Test/NotificationTests/GrowlProviderTest.cs similarity index 100% rename from NzbDrone.Core.Test/ProviderTests/GrowlProviderTest.cs rename to NzbDrone.Core.Test/NotificationTests/GrowlProviderTest.cs diff --git a/NzbDrone.Core.Test/NotificationTests/NotificationServiceFixture.cs b/NzbDrone.Core.Test/NotificationTests/NotificationServiceFixture.cs index 8ad4812f4..2c0d86add 100644 --- a/NzbDrone.Core.Test/NotificationTests/NotificationServiceFixture.cs +++ b/NzbDrone.Core.Test/NotificationTests/NotificationServiceFixture.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.NotificationTests { _notifications = new List(); - _notifications.Add(new Xbmc(null, null)); + _notifications.Add(new Notifications.Xbmc.Xbmc(null, null)); _notifications.Add(new PlexClient(null)); _notifications.Add(new PlexServer(null)); _notifications.Add(new Email(null)); @@ -49,8 +49,8 @@ namespace NzbDrone.Core.Test.NotificationTests { Mocker.SetConstant(Mocker.Resolve()); - Mocker.GetMock().Setup(s => s.Resolve(typeof (Xbmc))) - .Returns(new Xbmc(null, null)); + Mocker.GetMock().Setup(s => s.Resolve(typeof(Notifications.Xbmc.Xbmc))) + .Returns(new Notifications.Xbmc.Xbmc(null, null)); Mocker.GetMock().Setup(s => s.Resolve(typeof(PlexClient))) .Returns(new PlexClient(null)); diff --git a/NzbDrone.Core.Test/ProviderTests/PlexProviderTest.cs b/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs similarity index 100% rename from NzbDrone.Core.Test/ProviderTests/PlexProviderTest.cs rename to NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs diff --git a/NzbDrone.Core.Test/ProviderTests/ProwlProviderTest.cs b/NzbDrone.Core.Test/NotificationTests/ProwlProviderTest.cs similarity index 100% rename from NzbDrone.Core.Test/ProviderTests/ProwlProviderTest.cs rename to NzbDrone.Core.Test/NotificationTests/ProwlProviderTest.cs diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/GetJsonVersionFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/GetJsonVersionFixture.cs new file mode 100644 index 000000000..388c1cccc --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/GetJsonVersionFixture.cs @@ -0,0 +1,80 @@ +using System; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Model.Xbmc; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc +{ + [TestFixture] + public class GetJsonVersionFixture : CoreTest + { + private XbmcSettings _settings; + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + } + + [TestCase(3)] + [TestCase(2)] + [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); + + Subject.GetJsonVersion(_settings).Should().Be(new XbmcVersion(number)); + } + + [TestCase(5, 0, 0)] + [TestCase(6, 0, 0)] + [TestCase(6, 1, 0)] + [TestCase(6, 0, 23)] + [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); + + Subject.GetJsonVersion(_settings).Should().Be(new XbmcVersion(major, minor, patch)); + } + + [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); + + Subject.GetJsonVersion(_settings).Should().Be(new XbmcVersion(0)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/ActivePlayersFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/ActivePlayersFixture.cs new file mode 100644 index 000000000..ca412a3c1 --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/ActivePlayersFixture.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Http +{ + [TestFixture] + public class ActivePlayersFixture : CoreTest + { + private XbmcSettings _settings; + private string _expectedUrl; + + private void WithNoActivePlayers() + { + Mocker.GetMock() + .Setup(s => s.DownloadString(_expectedUrl, _settings.Username, _settings.Password)) + .Returns("
  • Filename:[Nothing Playing]"); + } + + private void WithVideoPlayerActive() + { + var activePlayers = @"
  • Filename:C:\Test\TV\2 Broke Girls\Season 01\2 Broke Girls - S01E01 - Pilot [SDTV].avi" + + "
  • PlayStatus:Playing
  • VideoNo:0
  • Type:Video
  • Thumb:special://masterprofile/Thumbnails/Video/a/auto-a664d5a2.tbn" + + "
  • Time:00:06
  • Duration:21:35
  • Percentage:0
  • File size:183182590
  • Changed:True"; + + Mocker.GetMock() + .Setup(s => s.DownloadString(_expectedUrl, _settings.Username, _settings.Password)) + .Returns(activePlayers); + } + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + + _expectedUrl = String.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", _settings.Address, "getcurrentlyplaying"); + } + + [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"); + } + } +} diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/CheckForErrorFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/CheckForErrorFixture.cs new file mode 100644 index 000000000..db4aeeb1d --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/CheckForErrorFixture.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Http +{ + [TestFixture] + public class CheckForErrorFixture : CoreTest + { + [Test] + public void should_be_true_when_the_response_contains_an_error() + { + const string response = "html>
  • Error:Unknown command"; + + 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 = "html>
  • Filename:[Nothing Playing]"; + + Subject.CheckForError(response).Should().BeFalse(); + } + } +} diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/GetSeriesPathFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/GetSeriesPathFixture.cs new file mode 100644 index 000000000..32861e067 --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/GetSeriesPathFixture.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Http +{ + [TestFixture] + public class GetSeriesPathFixture : CoreTest + { + private XbmcSettings _settings; + private Series _series; + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + + _series = new Series + { + TvdbId = 79488, + Title = "30 Rock" + }; + + const string setResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat(webheader;false;webfooter;false;header;;footer;;opentag;;closetag;;closefinaltag;false)"; + const string resetResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat()"; + + Mocker.GetMock() + .Setup(s => s.DownloadString(setResponseUrl, _settings.Username, _settings.Password)) + .Returns("OK"); + + Mocker.GetMock() + .Setup(s => s.DownloadString(resetResponseUrl, _settings.Username, _settings.Password)) + .Returns(@" +
  • OK + "); + } + + [Test] + public void should_get_series_path() + { + const string queryResult = @"smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/"; + var query = String.Format("http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"); + + Mocker.GetMock() + .Setup(s => s.DownloadString(query, _settings.Username, _settings.Password)) + .Returns(queryResult); + + Subject.GetSeriesPath(_settings, _series) + .Should().Be("smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/"); + } + + [Test] + public void should_get_null_for_series_path() + { + const string queryResult = @""; + var query = String.Format("http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"); + + Mocker.GetMock() + .Setup(s => s.DownloadString(query, _settings.Username, _settings.Password)) + .Returns(queryResult); + + + Subject.GetSeriesPath(_settings, _series) + .Should().BeNull(); + } + + [Test] + public void should_get_series_path_with_special_characters_in_it() + { + const string queryResult = @"smb://xbmc:xbmc@HOMESERVER/TV/Law & Order- Special Victims Unit/"; + var query = String.Format("http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"); + + Mocker.GetMock() + .Setup(s => s.DownloadString(query, _settings.Username, _settings.Password)) + .Returns(queryResult); + + + Subject.GetSeriesPath(_settings, _series) + .Should().Be("smb://xbmc:xbmc@HOMESERVER/TV/Law & Order- Special Victims Unit/"); + } + } +} diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/UpdateFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/UpdateFixture.cs new file mode 100644 index 000000000..2ecb1d8a4 --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/Http/UpdateFixture.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Http +{ + [TestFixture] + public class UpdateFixture : CoreTest + { + private XbmcSettings _settings; + private string _seriesQueryUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"; + private Series _fakeSeries; + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + + _fakeSeries = Builder.CreateNew() + .With(s => s.TvdbId = 79488) + .With(s => s.Title = "30 Rock") + .Build(); + } + + private void WithSeriesPath() + { + Mocker.GetMock() + .Setup(s => s.DownloadString(_seriesQueryUrl, _settings.Username, _settings.Password)) + .Returns("smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/"); + } + + private void WithoutSeriesPath() + { + Mocker.GetMock() + .Setup(s => s.DownloadString(_seriesQueryUrl, _settings.Username, _settings.Password)) + .Returns(""); + } + + [Test] + public void should_update_using_series_path() + { + WithSeriesPath(); + const string url = "http://localhost:8080/xbmcCmds/xbmcHttp?command=ExecBuiltIn(UpdateLibrary(video,smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/))"; + + Mocker.GetMock().Setup(s => s.DownloadString(url, _settings.Username, _settings.Password)); + + Subject.Update(_settings, _fakeSeries); + Mocker.VerifyAllMocks(); + } + + [Test] + public void should_update_all_paths_when_series_path_not_found() + { + WithoutSeriesPath(); + const string url = "http://localhost:8080/xbmcCmds/xbmcHttp?command=ExecBuiltIn(UpdateLibrary(video))"; + + Mocker.GetMock().Setup(s => s.DownloadString(url, _settings.Username, _settings.Password)); + + Subject.Update(_settings, _fakeSeries); + Mocker.VerifyAllMocks(); + } + } +} diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/HttpApiProviderFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/HttpApiProviderFixture.cs new file mode 100644 index 000000000..093b54b46 --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/HttpApiProviderFixture.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc +{ + [TestFixture] + public class HttpApiProviderFixture : CoreTest + { + private XbmcSettings _settings; + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + } + } +} diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/ActivePlayersFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/ActivePlayersFixture.cs new file mode 100644 index 000000000..250079575 --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/ActivePlayersFixture.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +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/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/CheckForErrorFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/CheckForErrorFixture.cs new file mode 100644 index 000000000..60a685727 --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/CheckForErrorFixture.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +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/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/GetSeriesPathFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/GetSeriesPathFixture.cs new file mode 100644 index 000000000..c1b8ab41e --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/GetSeriesPathFixture.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json +{ + [TestFixture] + public class GetSeriesPathFixture : CoreTest + { + private XbmcSettings _settings; + private Series _series; + private string _response; + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + + _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}]}}"; + + Mocker.GetMock() + .Setup( + s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, It.IsAny())) + .Returns(_response); + } + + private void WithMatchingTvdbId() + { + _series = new Series + { + TvdbId = 78461, + Title = "TV Show" + }; + } + + private void WithMatchingTitle() + { + _series = new Series + { + TvdbId = 1, + Title = "30 Rock" + }; + } + + private void WithoutMatchingSeries() + { + _series = new Series + { + TvdbId = 1, + Title = "Does not exist" + }; + } + + [Test] + public void should_return_null_when_series_is_not_found() + { + WithoutMatchingSeries(); + + Subject.GetSeriesPath(_settings, _series).Should().BeNull(); + } + + [Test] + public void should_return_path_when_tvdbId_matches() + { + WithMatchingTvdbId(); + + Subject.GetSeriesPath(_settings, _series).Should().Be("smb://HOMESERVER/TV/8 Simple Rules/"); + } + + [Test] + public void should_return_path_when_title_matches() + { + WithMatchingTitle(); + + Subject.GetSeriesPath(_settings, _series).Should().Be("smb://HOMESERVER/TV/30 Rock/"); + } + } +} diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/UpdateFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/UpdateFixture.cs new file mode 100644 index 000000000..0e117e1c6 --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/Json/UpdateFixture.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc.Json +{ + [TestFixture] + public class UpdateFixture : CoreTest + { + private XbmcSettings _settings; + const string _expectedJson = "{\"jsonrpc\":\"2.0\",\"method\":\"VideoLibrary.GetTvShows\",\"params\":{\"properties\":[\"file\",\"imdbnumber\"]},\"id\":10}"; + + 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}]}}"; + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + + Mocker.GetMock() + .Setup(s => s.PostCommand(_settings.Address, _settings.Username, _settings.Password, + It.Is(e => e.Replace(" ", "").Replace("\r\n", "").Replace("\t", "") == _expectedJson.Replace(" ", "")))) + .Returns(_tvshowsResponse); + } + + [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\"}"); + + 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.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\"}"); + + 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()); + } + } +} diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/JsonApiProviderFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/JsonApiProviderFixture.cs new file mode 100644 index 000000000..2ec385c2e --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/JsonApiProviderFixture.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc +{ + [TestFixture] + public class JsonApiProviderFixture : CoreTest + { + private XbmcSettings _settings; + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + } + } +} diff --git a/NzbDrone.Core.Test/NotificationTests/Xbmc/XbmcServiceFixture.cs b/NzbDrone.Core.Test/NotificationTests/Xbmc/XbmcServiceFixture.cs new file mode 100644 index 000000000..52c1b943c --- /dev/null +++ b/NzbDrone.Core.Test/NotificationTests/Xbmc/XbmcServiceFixture.cs @@ -0,0 +1,40 @@ + + +using System; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Notifications.Xbmc; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Model.Xbmc; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; + +namespace NzbDrone.Core.Test.NotificationTests.Xbmc +{ + [TestFixture] + public class XbmcProviderTest : CoreTest + { + private XbmcSettings _settings; + + [SetUp] + public void Setup() + { + _settings = new XbmcSettings + { + Host = "localhost", + Port = 8080, + Username = "xbmc", + Password = "xbmc", + AlwaysUpdate = false, + CleanLibrary = false, + UpdateLibrary = true + }; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index dc98fb0b9..bd60a304b 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -149,6 +149,17 @@ + + + + + + + + + + + @@ -163,7 +174,7 @@ - + @@ -175,8 +186,8 @@ - - + + @@ -187,8 +198,7 @@ - - + diff --git a/NzbDrone.Core.Test/ProviderTests/EventClientProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/EventClientProviderTest.cs deleted file mode 100644 index bb049c679..000000000 --- a/NzbDrone.Core.Test/ProviderTests/EventClientProviderTest.cs +++ /dev/null @@ -1,98 +0,0 @@ - - -using System; -using Moq; -using NUnit.Framework; -using NzbDrone.Common; -using NzbDrone.Core.Model.Xbmc; -using NzbDrone.Core.Notifications.Xbmc; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Test.Common.AutoMoq; - -namespace NzbDrone.Core.Test.ProviderTests -{ - [TestFixture] - - public class EventClientProviderTest : CoreTest - { - [Test] - public void SendNotification_true() - { - - - - var header = "NzbDrone Test"; - var message = "Test Message!"; - var address = "localhost"; - - var fakeUdp = Mocker.GetMock(); - fakeUdp.Setup(s => s.Send(address, UdpProvider.PacketType.Notification, It.IsAny())).Returns(true); - - - var result = Mocker.Resolve().SendNotification(header, message, IconType.Jpeg, "NzbDrone.jpg", address); - - - Assert.AreEqual(true, result); - } - - [Test] - public void SendNotification_false() - { - - - - var header = "NzbDrone Test"; - var message = "Test Message!"; - var address = "localhost"; - - var fakeUdp = Mocker.GetMock(); - fakeUdp.Setup(s => s.Send(address, UdpProvider.PacketType.Notification, It.IsAny())).Returns(false); - - - var result = Mocker.Resolve().SendNotification(header, message, IconType.Jpeg, "NzbDrone.jpg", address); - - - Assert.AreEqual(false, result); - } - - [Test] - public void SendAction_Update_true() - { - - - - var path = @"C:\Test\TV\30 Rock"; - var command = String.Format("ExecBuiltIn(UpdateLibrary(video,{0}))", path); - var address = "localhost"; - - var fakeUdp = Mocker.GetMock(); - fakeUdp.Setup(s => s.Send(address, UdpProvider.PacketType.Action, It.IsAny())).Returns(true); - - - var result = Mocker.Resolve().SendAction(address, ActionType.ExecBuiltin, command); - - - Assert.AreEqual(true, result); - } - - [Test] - public void SendAction_Update_false() - { - - - - var path = @"C:\Test\TV\30 Rock"; - var command = String.Format("ExecBuiltIn(UpdateLibrary(video,{0}))", path); - var address = "localhost"; - - var fakeUdp = Mocker.GetMock(); - fakeUdp.Setup(s => s.Send(address, UdpProvider.PacketType.Action, It.IsAny())).Returns(false); - - - var result = Mocker.Resolve().SendAction(address, ActionType.ExecBuiltin, command); - - - Assert.AreEqual(false, result); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/XbmcProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/XbmcProviderTest.cs deleted file mode 100644 index 52de941e3..000000000 --- a/NzbDrone.Core.Test/ProviderTests/XbmcProviderTest.cs +++ /dev/null @@ -1,637 +0,0 @@ - - -using System; -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.Notifications.Xbmc; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Model.Xbmc; -using NzbDrone.Core.Providers; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Test.Common.AutoMoq; - -namespace NzbDrone.Core.Test.ProviderTests -{ - [TestFixture] - - public class XbmcProviderTest : CoreTest - { - private XbmcSettings _settings; - private string EdenActivePlayers; - - private void WithNoActivePlayers() - { - EdenActivePlayers = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[]}"; - } - - private void WithVideoPlayerActive() - { - EdenActivePlayers = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[{\"playerid\":1,\"type\":\"video\"}]}"; - } - - private void WithAudioPlayerActive() - { - EdenActivePlayers = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[{\"playerid\":1,\"type\":\"audio\"}]}"; - } - - private void WithPicturePlayerActive() - { - EdenActivePlayers = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":[{\"playerid\":1,\"type\":\"picture\"}]}"; - } - - private void WithAllPlayersActive() - { - EdenActivePlayers = "{\"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, - AlwaysUpdate = false, - CleanLibrary = false, - UpdateLibrary = true - }; - } - - [Test] - public void JsonError_true() - { - - - var response = "{\"error\":{\"code\":-32601,\"message\":\"Method not found.\"},\"id\":10,\"jsonrpc\":\"2.0\"}"; - - - var result = Mocker.Resolve().CheckForJsonError(response); - - - Assert.AreEqual(true, result); - } - - [Test] - public void JsonError_true_empty_response() - { - - - var response = String.Empty; - - - var result = Mocker.Resolve().CheckForJsonError(response); - - - Assert.AreEqual(true, result); - } - - [Test] - public void JsonError_false() - { - - - var reposnse = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"version\":3}}"; - - - var result = Mocker.Resolve().CheckForJsonError(reposnse); - - - Assert.AreEqual(false, result); - } - - [TestCase(3)] - [TestCase(2)] - [TestCase(0)] - public void GetJsonVersionIntOnly(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); - - - var result = Mocker.Resolve().GetJsonVersion("localhost:8080", "xbmc", "xbmc"); - - - result.Should().Be(new XbmcVersion(number)); - } - - [TestCase(5, 0, 0)] - [TestCase(6, 0, 0)] - [TestCase(6, 1, 0)] - [TestCase(6, 0, 23)] - [TestCase(0, 0, 0)] - public void GetJsonVersionFrodo(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); - - - var result = Mocker.Resolve().GetJsonVersion("localhost:8080", "xbmc", "xbmc"); - - - result.Should().Be(new XbmcVersion(major, minor, patch)); - } - - [Test] - public void GetJsonVersion_error() - { - 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); - - - var result = Mocker.Resolve().GetJsonVersion("localhost:8080", "xbmc", "xbmc"); - - - result.Should().Be(new XbmcVersion(0)); - } - - [TestCase(false, false, false)] - [TestCase(true, true, true)] - [TestCase(true, false, false)] - [TestCase(true, true, false)] - [TestCase(false, true, false)] - [TestCase(false, true, true)] - [TestCase(false, false, true)] - [TestCase(true, false, true)] - public void GetActivePlayersDharma(bool audio, bool picture, bool video) - { - - var message = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"audio\":" - + audio.ToString().ToLower() - + ",\"picture\":" - + picture.ToString().ToLower() - + ",\"video\":" - + video.ToString().ToLower() - + "}}"; - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(message); - - - var result = Mocker.Resolve().GetActivePlayersDharma("localhost:8080", "xbmc", "xbmc"); - - - Assert.AreEqual(audio, result["audio"]); - Assert.AreEqual(picture, result["picture"]); - Assert.AreEqual(video, result["video"]); - } - - [Test] - public void GetActivePlayersEden_should_be_empty_when_no_active_players() - { - - WithNoActivePlayers(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(EdenActivePlayers); - - - var result = Mocker.Resolve().GetActivePlayersEden("localhost:8080", "xbmc", "xbmc"); - - - result.Should().BeEmpty(); - } - - [Test] - public void GetActivePlayersEden_should_have_active_video_player() - { - - WithVideoPlayerActive(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(EdenActivePlayers); - - - var result = Mocker.Resolve().GetActivePlayersEden("localhost:8080", "xbmc", "xbmc"); - - - result.Should().HaveCount(1); - result.First().Type.Should().Be("video"); - } - - [Test] - public void GetActivePlayersEden_should_have_active_audio_player() - { - - WithAudioPlayerActive(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(EdenActivePlayers); - - - var result = Mocker.Resolve().GetActivePlayersEden("localhost:8080", "xbmc", "xbmc"); - - - result.Should().HaveCount(1); - result.First().Type.Should().Be("audio"); - } - - [Test] - public void GetActivePlayersEden_should_have_active_picture_player() - { - - WithPicturePlayerActive(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(EdenActivePlayers); - - - var result = Mocker.Resolve().GetActivePlayersEden("localhost:8080", "xbmc", "xbmc"); - - - result.Should().HaveCount(1); - result.First().Type.Should().Be("picture"); - } - - [Test] - public void GetActivePlayersEden_should_have_all_players_active() - { - - WithAllPlayersActive(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(EdenActivePlayers); - - - var result = Mocker.Resolve().GetActivePlayersEden("localhost:8080", "xbmc", "xbmc"); - - - result.Should().HaveCount(3); - result.Select(a => a.PlayerId).Distinct().Should().HaveCount(3); - result.Select(a => a.Type).Distinct().Should().HaveCount(3); - } - - [Test] - public void GetTvShowsJson() - { - - - - var message = "{\"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}]}}"; - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny())) - .Returns(message); - - - var result = Mocker.Resolve().GetTvShowsJson("localhost:8080", "xbmc", "xbmc"); - - - Assert.AreEqual(5, result.Count); - result.Should().Contain(s => s.ImdbNumber == 79488); - } - - [Test] - public void Notify_true() - { - var header = "NzbDrone Test"; - var message = "Test Message!"; - - var fakeEventClient = Mocker.GetMock(); - fakeEventClient.Setup(s => s.SendNotification(header, message, IconType.Jpeg, "NzbDrone.jpg", "localhost")).Returns(true); - - Mocker.Resolve().Notify(_settings, header, message); - - - Mocker.VerifyAllMocks(); - } - - [Test] - public void SendCommand() - { - - - - var host = "localhost:8080"; - var command = "ExecBuiltIn(CleanLibrary(video))"; - var username = "xbmc"; - var password = "xbmc"; - - var url = String.Format("http://localhost:8080/xbmcCmds/xbmcHttp?command=ExecBuiltIn(CleanLibrary(video))"); - - //var fakeUdpProvider = Mocker.GetMock(); - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.DownloadString(url, username, password)).Returns("Ok\n"); - - - var result = Mocker.Resolve().SendCommand(host, command, username, username); - - - Mocker.VerifyAllMocks(); - Assert.AreEqual("Ok\n", result); - } - - [Test] - public void GetXbmcSeriesPath_true() - { - - - - var queryResult = @"smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/"; - - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - - var setResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat(webheader;false;webfooter;false;header;;footer;;opentag;;closetag;;closefinaltag;false)"; - var resetResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat()"; - var query = String.Format("http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"); - - - //var fakeUdpProvider = Mocker.GetMock(); - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.DownloadString(setResponseUrl, username, password)).Returns("OK"); - fakeHttp.Setup(s => s.DownloadString(resetResponseUrl, username, password)).Returns(@" -
  • OK - "); - fakeHttp.Setup(s => s.DownloadString(query, username, password)).Returns(queryResult); - - - var result = Mocker.Resolve().GetXbmcSeriesPath(host, 79488, username, username); - - - Mocker.VerifyAllMocks(); - Assert.AreEqual("smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/", result); - } - - [Test] - public void GetXbmcSeriesPath_false() - { - - - - var queryResult = @""; - - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - - var setResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat(webheader;false;webfooter;false;header;;footer;;opentag;;closetag;;closefinaltag;false)"; - var resetResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat()"; - var query = String.Format("http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"); - - - //var fakeUdpProvider = Mocker.GetMock(); - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.DownloadString(setResponseUrl, username, password)).Returns("OK"); - fakeHttp.Setup(s => s.DownloadString(resetResponseUrl, username, password)).Returns(@" -
  • OK - "); - fakeHttp.Setup(s => s.DownloadString(query, username, password)).Returns(queryResult); - - - var result = Mocker.Resolve().GetXbmcSeriesPath(host, 79488, username, username); - - - Mocker.VerifyAllMocks(); - Assert.AreEqual("", result); - } - - [Test] - public void GetXbmcSeriesPath_special_characters() - { - - - - var queryResult = @"smb://xbmc:xbmc@HOMESERVER/TV/Law & Order- Special Victims Unit/"; - - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - - var setResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat(webheader;false;webfooter;false;header;;footer;;opentag;;closetag;;closefinaltag;false)"; - var resetResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat()"; - var query = String.Format("http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"); - - - //var fakeUdpProvider = Mocker.GetMock(); - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.DownloadString(setResponseUrl, username, password)).Returns("OK"); - fakeHttp.Setup(s => s.DownloadString(resetResponseUrl, username, password)).Returns(@" -
  • OK - "); - fakeHttp.Setup(s => s.DownloadString(query, username, password)).Returns(queryResult); - - - var result = Mocker.Resolve().GetXbmcSeriesPath(host, 79488, username, username); - - - Mocker.VerifyAllMocks(); - result.Should().Be("smb://xbmc:xbmc@HOMESERVER/TV/Law & Order- Special Victims Unit/"); - } - - [Test] - public void Clean() - { - var fakeEventClient = Mocker.GetMock(); - fakeEventClient.Setup(s => s.SendAction("localhost", ActionType.ExecBuiltin, "ExecBuiltIn(CleanLibrary(video))")).Returns(true); - - Mocker.Resolve().Clean(_settings); - Mocker.VerifyAllMocks(); - } - - [Test] - public void UpdateWithHttp_Single() - { - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - var queryResult = @"smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/"; - var queryUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"; - var url = "http://localhost:8080/xbmcCmds/xbmcHttp?command=ExecBuiltIn(UpdateLibrary(video,smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/))"; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 79488) - .With(s => s.Title = "30 Rock") - .Build(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.DownloadString(queryUrl, username, password)).Returns(queryResult); - fakeHttp.Setup(s => s.DownloadString(url, username, password)); - - - Mocker.Resolve().UpdateWithHttp(fakeSeries, host, username, password); - - - Mocker.VerifyAllMocks(); - } - - [Test] - public void UpdateWithHttp_All() - { - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - var queryResult = @""; - var queryUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=QueryVideoDatabase(select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = 79488 and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath)"; - var url = "http://localhost:8080/xbmcCmds/xbmcHttp?command=ExecBuiltIn(UpdateLibrary(video))"; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 79488) - .With(s => s.Title = "30 Rock") - .Build(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.DownloadString(queryUrl, username, password)).Returns(queryResult); - fakeHttp.Setup(s => s.DownloadString(url, username, password)); - - - Mocker.Resolve().UpdateWithHttp(fakeSeries, host, username, password); - - - Mocker.VerifyAllMocks(); - } - - [Test] - public void UpdateWithJsonBuiltIn_Single() - { - - - - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - var expectedJson = "{\"jsonrpc\":\"2.0\",\"method\":\"VideoLibrary.GetTvShows\",\"params\":{\"properties\":[\"file\",\"imdbnumber\"]},\"id\":10}"; - var tvshows = "{\"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}]}}"; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 79488) - .With(s => s.Title = "30 Rock") - .Build(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand(host, username, password, It.Is(e => e.Replace(" ", "").Replace("\r\n", "").Replace("\t", "") == expectedJson.Replace(" ", "")))) - .Returns(tvshows); - - var command = "ExecBuiltIn(UpdateLibrary(video,smb://HOMESERVER/TV/30 Rock/))"; - var url = String.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", host, command); - - fakeHttp.Setup(s => s.DownloadString(url, username, password)).Returns("
  • OK"); - - - var result = Mocker.Resolve().UpdateWithJsonExecBuiltIn(fakeSeries, host, username, password); - - - result.Should().BeTrue(); - } - - [Test] - public void UpdateWithJsonBuiltIn_All() - { - - - - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - var expectedJson = "{\"jsonrpc\":\"2.0\",\"method\":\"VideoLibrary.GetTvShows\",\"params\":{\"properties\":[\"file\",\"imdbnumber\"]},\"id\":10}"; - var tvshows = "{\"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/90210/\",\"imdbnumber\":\"82716\",\"label\":\"90210\",\"tvshowid\":5}]}}"; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 79488) - .With(s => s.Title = "30 Rock") - .Build(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand(host, username, password, It.Is(e => e.Replace(" ", "").Replace("\r\n", "").Replace("\t", "") == expectedJson.Replace(" ", "")))) - .Returns(tvshows); - - var command = "ExecBuiltIn(UpdateLibrary(video))"; - var url = String.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", host, command); - - fakeHttp.Setup(s => s.DownloadString(url, username, password)).Returns("
  • OK"); - - //var fakeEventClient = Mocker.GetMock(); - //fakeEventClient.Setup(s => s.SendAction("localhost", ActionType.ExecBuiltin, "ExecBuiltIn(UpdateLibrary(video))")); - - - var result = Mocker.Resolve().UpdateWithJsonExecBuiltIn(fakeSeries, host, username, password); - - - result.Should().BeTrue(); - } - - [Test] - public void UpdateWithJsonVideoLibraryScan_Single() - { - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - var expectedJson = "{\"jsonrpc\":\"2.0\",\"method\":\"VideoLibrary.GetTvShows\",\"params\":{\"properties\":[\"file\",\"imdbnumber\"]},\"id\":10}"; - var tvshows = "{\"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}]}}"; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 79488) - .With(s => s.Title = "30 Rock") - .Build(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand(host, username, password, It.Is(e => e.Replace(" ", "").Replace("\r\n", "").Replace("\t", "") == expectedJson.Replace(" ", "")))) - .Returns(tvshows); - - fakeHttp.Setup(s => s.PostCommand(host, username, 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 result = Mocker.Resolve().UpdateWithJsonVideoLibraryScan(fakeSeries, host, username, password); - - - result.Should().BeTrue(); - } - - [Test] - public void UpdateWithJsonVideoLibraryScan_All() - { - var host = "localhost:8080"; - var username = "xbmc"; - var password = "xbmc"; - var expectedJson = "{\"jsonrpc\":\"2.0\",\"method\":\"VideoLibrary.GetTvShows\",\"params\":{\"properties\":[\"file\",\"imdbnumber\"]},\"id\":10}"; - var tvshows = "{\"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/90210/\",\"imdbnumber\":\"82716\",\"label\":\"90210\",\"tvshowid\":5}]}}"; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Id = 79488) - .With(s => s.Title = "30 Rock") - .Build(); - - var fakeHttp = Mocker.GetMock(); - fakeHttp.Setup(s => s.PostCommand(host, username, password, It.Is(e => e.Replace(" ", "").Replace("\r\n", "").Replace("\t", "") == expectedJson.Replace(" ", "")))) - .Returns(tvshows); - - fakeHttp.Setup(s => s.PostCommand(host, username, 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 result = Mocker.Resolve().UpdateWithJsonVideoLibraryScan(fakeSeries, host, username, password); - - - result.Should().BeTrue(); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Model/Xbmc/ActionType.cs b/NzbDrone.Core/Model/Xbmc/ActionType.cs deleted file mode 100644 index 3dd6a6c19..000000000 --- a/NzbDrone.Core/Model/Xbmc/ActionType.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace NzbDrone.Core.Model.Xbmc -{ - public enum ActionType - { - ExecBuiltin = 0x01, - Button = 0x02 - } -} diff --git a/NzbDrone.Core/Model/Xbmc/ActivePlayersEdenResult.cs b/NzbDrone.Core/Model/Xbmc/ActivePlayersEdenResult.cs index af4a4c7f8..d57022f1b 100644 --- a/NzbDrone.Core/Model/Xbmc/ActivePlayersEdenResult.cs +++ b/NzbDrone.Core/Model/Xbmc/ActivePlayersEdenResult.cs @@ -15,5 +15,11 @@ namespace NzbDrone.Core.Model.Xbmc { public int PlayerId { get; set; } public string Type { get; set; } + + public ActivePlayer(int playerId, string type) + { + PlayerId = playerId; + Type = type; + } } } diff --git a/NzbDrone.Core/Model/Xbmc/IconType.cs b/NzbDrone.Core/Model/Xbmc/IconType.cs deleted file mode 100644 index f6ec678fa..000000000 --- a/NzbDrone.Core/Model/Xbmc/IconType.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace NzbDrone.Core.Model.Xbmc -{ - public enum IconType - { - None = 0x00, - Jpeg = 0x01, - Png = 0x02, - Gif = 0x03 - } -} diff --git a/NzbDrone.Core/Notifications/Xbmc/EventClientProvider.cs b/NzbDrone.Core/Notifications/Xbmc/EventClientProvider.cs deleted file mode 100644 index 738ab9281..000000000 --- a/NzbDrone.Core/Notifications/Xbmc/EventClientProvider.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using NzbDrone.Common; -using NzbDrone.Core.Model.Xbmc; - -namespace NzbDrone.Core.Notifications.Xbmc -{ - public class EventClientProvider - { - private readonly UdpProvider _udpProvider; - - public EventClientProvider(UdpProvider udpProvider) - { - _udpProvider = udpProvider; - } - - public EventClientProvider() - { - } - - public virtual bool SendNotification(string caption, string message, IconType iconType, string iconFile, string address) - { - byte[] icon = new byte[0]; - if (iconType != IconType.None) - { - icon = ResourceManager.GetRawLogo(iconFile); - } - - byte[] payload = new byte[caption.Length + message.Length + 7 + icon.Length]; - - int offset = 0; - - for (int i = 0; i < caption.Length; i++) - payload[offset++] = (byte)caption[i]; - payload[offset++] = (byte)'\0'; - - for (int i = 0; i < message.Length; i++) - payload[offset++] = (byte)message[i]; - payload[offset++] = (byte)'\0'; - - payload[offset++] = (byte)iconType; - - for (int i = 0; i < 4; i++) - payload[offset++] = 0; - - Array.Copy(icon, 0, payload, caption.Length + message.Length + 7, icon.Length); - - return _udpProvider.Send(address, UdpProvider.PacketType.Notification, payload); - } - - public virtual bool SendAction(string address, ActionType action, string messages) - { - var payload = new byte[messages.Length + 2]; - int offset = 0; - payload[offset++] = (byte)action; - - for (int i = 0; i < messages.Length; i++) - payload[offset++] = (byte)messages[i]; - - payload[offset++] = (byte)'\0'; - - return _udpProvider.Send(address, UdpProvider.PacketType.Action, payload); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs b/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs new file mode 100644 index 000000000..fc1948ef7 --- /dev/null +++ b/NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using NLog; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NzbDrone.Common; +using NzbDrone.Core.Model.Xbmc; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Notifications.Xbmc +{ + public class HttpApiProvider : IApiProvider + { + private readonly IHttpProvider _httpProvider; + private readonly Logger _logger; + + public HttpApiProvider(IHttpProvider httpProvider, Logger logger) + { + _httpProvider = httpProvider; + _logger = logger; + } + + public void Notify(XbmcSettings settings, string title, string message) + { + var notification = String.Format("Notification({0},{1},{2},{3})", title, message, 5000, "https://raw.github.com/NzbDrone/NzbDrone/vnext/NzbDrone.Core/NzbDrone.jpg"); + var command = BuildExecBuiltInCommand(notification); + + SendCommand(settings, command); + } + + public void Update(XbmcSettings settings, Series series) + { + if (!settings.AlwaysUpdate) + { + _logger.Trace("Determining if there are any active players on XBMC host: {0}", settings.Address); + var activePlayers = GetActivePlayers(settings); + + if (activePlayers.Any(a => a.Type.Equals("video"))) + { + _logger.Debug("Video is currently playing, skipping library update"); + return; + } + } + + UpdateLibrary(settings, series); + } + + public void Clean(XbmcSettings settings) + { + const string cleanVideoLibrary = "CleanLibrary(video)"; + var command = BuildExecBuiltInCommand(cleanVideoLibrary); + + SendCommand(settings, command); + } + + public List GetActivePlayers(XbmcSettings settings) + { + try + { + var result = new List(); + var response = SendCommand(settings, "getcurrentlyplaying"); + + if (response.Contains("
  • Filename:[Nothing Playing]")) return new List(); + if (response.Contains("
  • Type:Video")) result.Add(new ActivePlayer(1, "video")); + + return result; + } + + catch (Exception ex) + { + _logger.DebugException(ex.Message, ex); + } + + return new List(); + } + + public bool CheckForError(string response) + { + _logger.Trace("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.Trace("Error found in response: {0}", errorMessage); + return true; + } + + return false; + } + + public string GetSeriesPath(XbmcSettings settings, Series series) + { + var query = + String.Format( + "select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = {0} and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath", + series.TvdbId); + var command = String.Format("QueryVideoDatabase({0})", query); + + const string setResponseCommand = + "SetResponseFormat(webheader;false;webfooter;false;header;;footer;;opentag;;closetag;;closefinaltag;false)"; + const string resetResponseCommand = "SetResponseFormat()"; + + SendCommand(settings, setResponseCommand); + var response = SendCommand(settings, command); + SendCommand(settings, resetResponseCommand); + + if (String.IsNullOrEmpty(response)) + return String.Empty; + + var xDoc = XDocument.Load(new StringReader(response.Replace("&", "&"))); + var xml = (from x in xDoc.Descendants("xml") select x).FirstOrDefault(); + + if (xml == null) + return null; + + var field = xml.Descendants("field").FirstOrDefault(); + + if (field == null) + return null; + + return field.Value; + } + + public bool CanHandle(XbmcVersion version) + { + return version < new XbmcVersion(5); + } + + private void UpdateLibrary(XbmcSettings settings, Series series) + { + try + { + _logger.Trace("Sending Update DB Request to XBMC Host: {0}", settings.Address); + var xbmcSeriesPath = GetSeriesPath(settings, series); + + //If the path is found update it, else update the whole library + if (!String.IsNullOrEmpty(xbmcSeriesPath)) + { + _logger.Trace("Updating series [{0}] on XBMC host: {1}", series.Title, settings.Address); + var command = BuildExecBuiltInCommand(String.Format("UpdateLibrary(video,{0})", xbmcSeriesPath)); + SendCommand(settings, command); + } + + else + { + //Update the entire library + _logger.Trace("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", series.Title, settings.Address); + var command = BuildExecBuiltInCommand("UpdateLibrary(video)"); + SendCommand(settings, command); + } + } + + catch (Exception ex) + { + _logger.DebugException(ex.Message, ex); + } + } + + private string SendCommand(XbmcSettings settings, string command) + { + var url = String.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", settings.Address, command); + + if (!String.IsNullOrEmpty(settings.Username)) + { + return _httpProvider.DownloadString(url, settings.Username, settings.Password); + } + + return _httpProvider.DownloadString(url); + } + + private string BuildExecBuiltInCommand(string command) + { + return String.Format("ExecBuiltIn({0})", command); + } + } +} diff --git a/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs b/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs new file mode 100644 index 000000000..78dbcae1a --- /dev/null +++ b/NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Core.Model.Xbmc; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Notifications.Xbmc +{ + public interface IApiProvider + { + 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/NzbDrone.Core/Notifications/Xbmc/InvalidXbmcVersionException.cs b/NzbDrone.Core/Notifications/Xbmc/InvalidXbmcVersionException.cs new file mode 100644 index 000000000..d77acd189 --- /dev/null +++ b/NzbDrone.Core/Notifications/Xbmc/InvalidXbmcVersionException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Notifications.Xbmc +{ + public class InvalidXbmcVersionException : Exception + { + public InvalidXbmcVersionException() + { + } + + public InvalidXbmcVersionException(string message) : base(message) + { + } + } +} diff --git a/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs b/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs new file mode 100644 index 000000000..f70a69e5b --- /dev/null +++ b/NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NzbDrone.Common; +using NzbDrone.Core.Model.Xbmc; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Notifications.Xbmc +{ + public class JsonApiProvider : IApiProvider + { + private readonly IHttpProvider _httpProvider; + private readonly Logger _logger; + + public JsonApiProvider(IHttpProvider httpProvider, Logger logger) + { + _httpProvider = httpProvider; + _logger = logger; + } + + public void Notify(XbmcSettings settings, string title, string message) + { + var parameters = new JObject( + new JProperty("title", title), + new JProperty("message", message), + new JProperty("image", "https://raw.github.com/NzbDrone/NzbDrone/vnext/NzbDrone.Core/NzbDrone.jpg")); + + var postJson = BuildJsonRequest("GUI.ShowNotification", parameters); + + _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); + } + + public void Update(XbmcSettings settings, Series series) + { + if (!settings.AlwaysUpdate) + { + _logger.Trace("Determining if there are any active players on XBMC host: {0}", settings.Address); + var activePlayers = GetActivePlayers(settings); + + if (activePlayers.Any(a => a.Type.Equals("video"))) + { + _logger.Debug("Video is currently playing, skipping library update"); + return; + } + } + + UpdateLibrary(settings, series); + } + + public void Clean(XbmcSettings settings) + { + var postJson = BuildJsonRequest("VideoLibrary.Clean"); + + _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); + } + + 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 = JsonConvert.DeserializeObject(response); + + return result.Result; + } + + catch (Exception ex) + { + _logger.DebugException(ex.Message, ex); + } + + return new List(); + } + + public bool CheckForError(string response) + { + _logger.Trace("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 = JsonConvert.DeserializeObject(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; + } + + public string GetSeriesPath(XbmcSettings settings, Series series) + { + var allSeries = GetSeries(settings); + + if (!allSeries.Any()) + { + _logger.Trace("No TV shows returned from XBMC"); + return null; + } + + else + { + var matchingSeries = allSeries.FirstOrDefault(s => s.ImdbNumber == series.TvdbId || s.Label == series.Title); + + if (matchingSeries != null) return matchingSeries.File; + } + + return null; + } + + public bool CanHandle(XbmcVersion version) + { + return version >= new XbmcVersion(5); + } + + private void UpdateLibrary(XbmcSettings settings, Series series) + { + try + { + var seriesPath = GetSeriesPath(settings, series); + + JObject postJson; + + if (seriesPath != null) + { + _logger.Trace("Updating series [{0}] (Path: {1}) on XBMC host: {2}", series.Title, seriesPath, settings.Address); + + var parameters = new JObject(new JObject(new JProperty("directory", seriesPath))); + postJson = BuildJsonRequest("VideoLibrary.Scan", parameters); + } + + else + { + _logger.Trace("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", series.Title, + settings.Address); + + postJson = BuildJsonRequest("VideoLibrary.Scan"); + } + + var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); + + if (CheckForError(response)) return; + + _logger.Trace(" from response"); + var result = JsonConvert.DeserializeObject>(response); + + if (!result.Result.Equals("OK", StringComparison.InvariantCultureIgnoreCase)) + { + _logger.Trace("Failed to update library for: {0}", settings.Address); + } + } + + catch (Exception ex) + { + _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 = JsonConvert.DeserializeObject(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", 10)); + + return postJson; + } + } +} diff --git a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs new file mode 100644 index 000000000..f49ea6ded --- /dev/null +++ b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs @@ -0,0 +1,12 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Notifications.Xbmc +{ + public class TestXbmcCommand : ICommand + { + public string Host { get; set; } + public int Port { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } +} diff --git a/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs b/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs index 113396ef1..844adf0b1 100644 --- a/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs +++ b/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs @@ -5,9 +5,9 @@ namespace NzbDrone.Core.Notifications.Xbmc { public class Xbmc : NotificationBase { - private readonly XbmcProvider _xbmcProvider; + private readonly IXbmcService _xbmcProvider; - public Xbmc(XbmcProvider xbmcProvider, Logger logger) + public Xbmc(IXbmcService xbmcProvider, Logger logger) { _xbmcProvider = xbmcProvider; } diff --git a/NzbDrone.Core/Notifications/Xbmc/XbmcProvider.cs b/NzbDrone.Core/Notifications/Xbmc/XbmcProvider.cs deleted file mode 100644 index 8facdc754..000000000 --- a/NzbDrone.Core/Notifications/Xbmc/XbmcProvider.cs +++ /dev/null @@ -1,455 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NLog; -using NzbDrone.Common; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Model.Xbmc; - -namespace NzbDrone.Core.Notifications.Xbmc -{ - public class XbmcProvider - { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private readonly IHttpProvider _httpProvider; - private readonly EventClientProvider _eventClientProvider; - - public XbmcProvider(IHttpProvider httpProvider, EventClientProvider eventClientProvider) - { - _httpProvider = httpProvider; - _eventClientProvider = eventClientProvider; - } - - public virtual void Notify(XbmcSettings settings, string header, string message) - { - //Always use EventServer, until Json has real support for it - var host = settings.Host; - Logger.Trace("Sending Notifcation to XBMC Host: {0}", host); - _eventClientProvider.SendNotification(header, message, IconType.Jpeg, "NzbDrone.jpg", host); - } - - public virtual void Update(XbmcSettings settings, Series series) - { - //Use Json for Eden/Nightly or depricated HTTP for 10.x (Dharma) to get the proper path - //Perform update with EventServer (Json currently doesn't support updating a specific path only - July 2011) - - var username = settings.Username; - var password = settings.Password; - var address = String.Format("{0}:{1}", settings.Host, settings.Port); - - Logger.Trace("Determining version of XBMC Host: {0}", address); - var version = GetJsonVersion(address, username, password); - - //Dharma - if (version == new XbmcVersion(2)) - { - //Check for active player only when we should skip updates when playing - if (!settings.AlwaysUpdate) - { - Logger.Trace("Determining if there are any active players on XBMC host: {0}", address); - var activePlayers = GetActivePlayersDharma(address, username, password); - - //If video is currently playing, then skip update - if (activePlayers["video"]) - { - Logger.Debug("Video is currently playing, skipping library update"); - return; - } - } - UpdateWithHttp(series, address, username, password); - } - - //Eden - else if (version == new XbmcVersion(3) || version == new XbmcVersion(4)) - { - //Check for active player only when we should skip updates when playing - if (!settings.AlwaysUpdate) - { - Logger.Trace("Determining if there are any active players on XBMC host: {0}", address); - var activePlayers = GetActivePlayersEden(address, username, password); - - //If video is currently playing, then skip update - if (activePlayers.Any(a => a.Type.Equals("video"))) - { - Logger.Debug("Video is currently playing, skipping library update"); - return; - } - } - - UpdateWithJsonExecBuiltIn(series, address, username, password); - } - - //Frodo or newer (attempting to make it future compatible) - else if (version >= new XbmcVersion(5)) - { - //Check for active player only when we should skip updates when playing - if (!settings.AlwaysUpdate) - { - Logger.Trace("Determining if there are any active players on XBMC host: {0}", address); - var activePlayers = GetActivePlayersEden(address, username, password); - - //If video is currently playing, then skip update - if (activePlayers.Any(a => a.Type.Equals("video"))) - { - Logger.Debug("Video is currently playing, skipping library update"); - return; - } - } - - UpdateWithJsonVideoLibraryScan(series, address, username, password); - } - - //Log Version zero if check failed - else - Logger.Trace("Unknown version: [{0}], skipping.", version); - - } - - public virtual bool UpdateWithJsonExecBuiltIn(Series series, string host, string username, string password) - { - try - { - //Use Json! - var xbmcShows = GetTvShowsJson(host, username, password); - - TvShow path = null; - - //Log if response is null, otherwise try to find XBMC's path for series - if (xbmcShows == null) - Logger.Trace("Failed to get TV Shows from XBMC"); - - else - path = xbmcShows.FirstOrDefault(s => s.ImdbNumber == series.Id || s.Label == series.Title); - - //var hostOnly = GetHostWithoutPort(host); - - if (path != null) - { - Logger.Trace("Updating series [{0}] (Path: {1}) on XBMC host: {2}", series.Title, path.File, host); - var command = String.Format("ExecBuiltIn(UpdateLibrary(video,{0}))", path.File); - SendCommand(host, command, username, password); - } - - else - { - Logger.Trace("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", series.Title, host); - SendCommand(host, "ExecBuiltIn(UpdateLibrary(video))", username, password); - } - } - - catch (Exception ex) - { - Logger.DebugException(ex.Message, ex); - return false; - } - - return true; - } - - public virtual bool UpdateWithJsonVideoLibraryScan(Series series, string host, string username, string password) - { - try - { - //Use Json! - var xbmcShows = GetTvShowsJson(host, username, password); - - TvShow path = null; - - //Log if response is null, otherwise try to find XBMC's path for series - if (xbmcShows == null) - Logger.Trace("Failed to get TV Shows from XBMC"); - - else - path = xbmcShows.FirstOrDefault(s => s.ImdbNumber == series.TvdbId || s.Label == series.Title); - - var postJson = new JObject(); - postJson.Add(new JProperty("jsonrpc", "2.0")); - postJson.Add(new JProperty("method", "VideoLibrary.Scan")); - postJson.Add(new JProperty("id", 55)); - - if (path != null) - { - Logger.Trace("Updating series [{0}] (Path: {1}) on XBMC host: {2}", series.Title, path.File, host); - postJson.Add(new JProperty("params", new JObject(new JObject(new JProperty("directory", path.File))))); - } - - else - Logger.Trace("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", series.Title, host); - - var response = _httpProvider.PostCommand(host, username, password, postJson.ToString()); - - if (CheckForJsonError(response)) - return false; - - Logger.Trace(" from response"); - var result = JsonConvert.DeserializeObject>(response); - - if (!result.Result.Equals("OK", StringComparison.InvariantCultureIgnoreCase)) - return false; - } - - catch (Exception ex) - { - Logger.DebugException(ex.Message, ex); - return false; - } - - return true; - } - - public virtual bool UpdateWithHttp(Series series, string host, string username, string password) - { - try - { - Logger.Trace("Sending Update DB Request to XBMC Host: {0}", host); - var xbmcSeriesPath = GetXbmcSeriesPath(host, series.Id, username, password); - - //If the path is found update it, else update the whole library - if (!String.IsNullOrEmpty(xbmcSeriesPath)) - { - Logger.Trace("Updating series [{0}] on XBMC host: {1}", series.Title, host); - var command = String.Format("ExecBuiltIn(UpdateLibrary(video,{0}))", xbmcSeriesPath); - SendCommand(host, command, username, password); - } - - else - { - //Update the entire library - Logger.Trace("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", series.Title, host); - SendCommand(host, "ExecBuiltIn(UpdateLibrary(video))", username, password); - } - } - - catch (Exception ex) - { - Logger.DebugException(ex.Message, ex); - return false; - } - - return true; - } - - public virtual void Clean(XbmcSettings settings) - { - //Use EventServer, once Dharma is extinct use Json? - - var host = settings.Host; - Logger.Trace("Sending DB Clean Request to XBMC Host: {0}", host); - var command = "ExecBuiltIn(CleanLibrary(video))"; - _eventClientProvider.SendAction(host, ActionType.ExecBuiltin, command); - } - - public virtual string SendCommand(string host, string command, string username, string password) - { - var url = String.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", host, command); - - if (!String.IsNullOrEmpty(username)) - { - return _httpProvider.DownloadString(url, username, password); - } - - return _httpProvider.DownloadString(url); - } - - public virtual string GetXbmcSeriesPath(string host, int seriesId, string username, string password) - { - var query = - String.Format( - "select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = {0} and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath", - seriesId); - var command = String.Format("QueryVideoDatabase({0})", query); - - const string setResponseCommand = - "SetResponseFormat(webheader;false;webfooter;false;header;;footer;;opentag;;closetag;;closefinaltag;false)"; - const string resetResponseCommand = "SetResponseFormat()"; - - SendCommand(host, setResponseCommand, username, password); - var response = SendCommand(host, command, username, password); - SendCommand(host, resetResponseCommand, username, password); - - if (String.IsNullOrEmpty(response)) - return String.Empty; - - var xDoc = XDocument.Load(new StringReader(response.Replace("&", "&"))); - var xml = (from x in xDoc.Descendants("xml") select x).FirstOrDefault(); - - if (xml == null) - return String.Empty; - - var field = xml.Descendants("field").FirstOrDefault(); - - if (field == null) - return String.Empty; - - return field.Value; - } - - public virtual XbmcVersion GetJsonVersion(string host, string username, string password) - { - //2 = Dharma - //3 & 4 = Eden - //5 & 6 = Frodo - - try - { - var postJson = new JObject(); - postJson.Add(new JProperty("jsonrpc", "2.0")); - postJson.Add(new JProperty("method", "JSONRPC.Version")); - postJson.Add(new JProperty("id", 10)); - - var response = _httpProvider.PostCommand(host, username, password, postJson.ToString()); - - if (CheckForJsonError(response)) - return new XbmcVersion(); - - Logger.Trace("Getting version from response"); - var result = JsonConvert.DeserializeObject>(response); - - var versionObject = result.Result.Property("version"); - - if (versionObject.Value.Type == JTokenType.Integer) - return new XbmcVersion((int)versionObject.Value); - - if (versionObject.Value.Type == JTokenType.Object) - return JsonConvert.DeserializeObject(versionObject.Value.ToString()); - - throw new InvalidCastException("Unknown Version structure!: " + versionObject); - } - - catch (Exception ex) - { - Logger.DebugException(ex.Message, ex); - } - - return new XbmcVersion(); - } - - public virtual Dictionary GetActivePlayersDharma(string host, string username, string password) - { - 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(host, username, password, postJson.ToString()); - - if (CheckForJsonError(response)) - return null; - - var result = JsonConvert.DeserializeObject(response); - - return result.Result; - } - - catch (Exception ex) - { - Logger.DebugException(ex.Message, ex); - } - - return null; - } - - public virtual List GetActivePlayersEden(string host, string username, string password) - { - 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(host, username, password, postJson.ToString()); - - if (CheckForJsonError(response)) - return null; - - var result = JsonConvert.DeserializeObject(response); - - return result.Result; - } - - catch (Exception ex) - { - Logger.DebugException(ex.Message, ex); - } - - return null; - } - - public virtual List GetTvShowsJson(string host, string username, string password) - { - try - { - var postJson = new JObject(); - postJson.Add(new JProperty("jsonrpc", "2.0")); - postJson.Add(new JProperty("method", "VideoLibrary.GetTvShows")); - postJson.Add(new JProperty("params", new JObject { new JProperty("properties", new string[] { "file", "imdbnumber" }) })); - postJson.Add(new JProperty("id", 10)); - - var response = _httpProvider.PostCommand(host, username, password, postJson.ToString()); - - if (CheckForJsonError(response)) - return null; - - var result = JsonConvert.DeserializeObject(response); - var shows = result.Result.TvShows; - - return shows; - } - catch (Exception ex) - { - Logger.DebugException(ex.Message, ex); - } - return null; - } - - public virtual bool CheckForJsonError(string response) - { - Logger.Trace("Looking for error in response: {0}", response); - - if (response.StartsWith("{\"error\"")) - { - var error = JsonConvert.DeserializeObject(response); - var code = error.Error["code"]; - var message = error.Error["message"]; - - Logger.Debug("XBMC Json Error. Code = {0}, Message: {1}", code, message); - return true; - } - - if (String.IsNullOrWhiteSpace(response)) - { - Logger.Debug("Invalid response from XBMC, the response is not valid JSON"); - return true; - } - - return false; - } - - public virtual void TestNotification(string hosts) - { - foreach (var host in hosts.Split(',')) - { - Logger.Trace("Sending Test Notifcation to XBMC Host: {0}", host); - _eventClientProvider.SendNotification("Test Notification", "Success! Notifications are setup correctly", IconType.Jpeg, "NzbDrone.jpg", host); - } - } - - public virtual void TestJsonApi(string hosts, string username, string password) - { - foreach (var host in hosts.Split(',')) - { - Logger.Trace("Sending Test Notifcation to XBMC Host: {0}", host); - var version = GetJsonVersion(host, username, password); - if (version == new XbmcVersion()) - throw new Exception("Failed to get JSON version in test"); - } - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs b/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs new file mode 100644 index 000000000..4f1761477 --- /dev/null +++ b/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Model.Xbmc; + +namespace NzbDrone.Core.Notifications.Xbmc +{ + public interface IXbmcService + { + void Notify(XbmcSettings settings, string title, string message); + void Update(XbmcSettings settings, Series series); + void Clean(XbmcSettings settings); + XbmcVersion GetJsonVersion(XbmcSettings settings); + } + + public class XbmcService : IXbmcService, IExecute + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private readonly IHttpProvider _httpProvider; + private readonly IEnumerable _apiProviders; + + public XbmcService(IHttpProvider httpProvider, IEnumerable apiProviders) + { + _httpProvider = httpProvider; + _apiProviders = apiProviders; + } + + public void Notify(XbmcSettings settings, string title, string message) + { + var provider = GetApiProvider(settings); + provider.Notify(settings, title, message); + } + + public void Update(XbmcSettings settings, Series series) + { + var provider = GetApiProvider(settings); + provider.Update(settings, series); + } + + public void Clean(XbmcSettings settings) + { + var provider = GetApiProvider(settings); + provider.Clean(settings); + } + + public XbmcVersion GetJsonVersion(XbmcSettings settings) + { + try + { + var postJson = new JObject(); + postJson.Add(new JProperty("jsonrpc", "2.0")); + postJson.Add(new JProperty("method", "JSONRPC.Version")); + postJson.Add(new JProperty("id", 10)); + + var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); + + Logger.Trace("Getting version from response"); + var result = JsonConvert.DeserializeObject>(response); + + var versionObject = result.Result.Property("version"); + + if (versionObject.Value.Type == JTokenType.Integer) + return new XbmcVersion((int)versionObject.Value); + + if (versionObject.Value.Type == JTokenType.Object) + return JsonConvert.DeserializeObject(versionObject.Value.ToString()); + + throw new InvalidCastException("Unknown Version structure!: " + versionObject); + } + + catch (Exception ex) + { + Logger.DebugException(ex.Message, ex); + } + + return new XbmcVersion(); + } + + private IApiProvider GetApiProvider(XbmcSettings settings) + { + var version = GetJsonVersion(settings); + var apiProvider = _apiProviders.SingleOrDefault(a => a.CanHandle(version)); + + if (apiProvider == null) + { + var message = String.Format("Invalid API Version: {0} for {1}", version, settings.Address); + throw new InvalidXbmcVersionException(message); + } + + return apiProvider; + } + + public void Execute(TestXbmcCommand message) + { + var settings = new XbmcSettings + { + Host = message.Host, + Port = message.Port, + Username = message.Username, + Password = message.Password + }; + + Logger.Trace("Determining version of XBMC Host: {0}", settings.Address); + var version = GetJsonVersion(settings); + Logger.Trace("Version is: {0}", version); + + Notify(settings, "Test Notification", "Success! XBMC has been successfully configured!"); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs b/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs index 110b2cc55..e42ca7598 100644 --- a/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs +++ b/NzbDrone.Core/Notifications/Xbmc/XbmcSettings.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Newtonsoft.Json; using NzbDrone.Core.Annotations; namespace NzbDrone.Core.Notifications.Xbmc @@ -32,6 +33,9 @@ namespace NzbDrone.Core.Notifications.Xbmc [FieldDefinition(7, Label = "Always Update", HelpText = "Update Library even when a video is playing?", Type = FieldType.Checkbox)] public Boolean AlwaysUpdate { get; set; } + [JsonIgnore] + public String Address { get { return String.Format("{0}:{1}", Host, Port); } } + public bool IsValid { get diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 8596816b0..146b7d15d 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -329,6 +329,11 @@ + + + + + @@ -379,12 +384,10 @@ - - @@ -492,10 +495,7 @@ Code - - Code - - + Code diff --git a/UI/.idea/runConfigurations/Debug___Chrome.xml b/UI/.idea/runConfigurations/Debug___Chrome.xml index 2323f096d..4ad681dfa 100644 --- a/UI/.idea/runConfigurations/Debug___Chrome.xml +++ b/UI/.idea/runConfigurations/Debug___Chrome.xml @@ -3,22 +3,22 @@ diff --git a/UI/.idea/runConfigurations/Debug___Firefox.xml b/UI/.idea/runConfigurations/Debug___Firefox.xml index 1f0cbd78b..599880f12 100644 --- a/UI/.idea/runConfigurations/Debug___Firefox.xml +++ b/UI/.idea/runConfigurations/Debug___Firefox.xml @@ -3,22 +3,22 @@