Merge branch 'markus101'

pull/4/head
kay.one 13 years ago
commit 291e2c399e

@ -0,0 +1,103 @@
// ReSharper disable RedundantUsingDirective
using System;
using System.Collections.Generic;
using AutoMoq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Model.Xbmc;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Providers.Xbmc;
using NzbDrone.Core.Repository;
using NzbDrone.Core.Repository.Quality;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test
{
[TestFixture]
// ReSharper disable InconsistentNaming
public class EventClientProviderTest : TestBase
{
[Test]
public void SendNotification_true()
{
//Setup
var mocker = new AutoMoqer();
var header = "NzbDrone Test";
var message = "Test Message!";
var address = "localhost";
var fakeUdp = mocker.GetMock<UdpProvider>();
fakeUdp.Setup(s => s.Send(address, UdpProvider.PacketType.Notification, It.IsAny<byte[]>())).Returns(true);
//Act
var result = mocker.Resolve<EventClientProvider>().SendNotification(header, message, IconType.Jpeg, "NzbDrone.jpg", address);
//Assert
Assert.AreEqual(true, result);
}
[Test]
public void SendNotification_false()
{
//Setup
var mocker = new AutoMoqer();
var header = "NzbDrone Test";
var message = "Test Message!";
var address = "localhost";
var fakeUdp = mocker.GetMock<UdpProvider>();
fakeUdp.Setup(s => s.Send(address, UdpProvider.PacketType.Notification, It.IsAny<byte[]>())).Returns(false);
//Act
var result = mocker.Resolve<EventClientProvider>().SendNotification(header, message, IconType.Jpeg, "NzbDrone.jpg", address);
//Assert
Assert.AreEqual(false, result);
}
[Test]
public void SendAction_Update_true()
{
//Setup
var mocker = new AutoMoqer();
var path = @"C:\Test\TV\30 Rock";
var command = String.Format("ExecBuiltIn(UpdateLibrary(video,{0}))", path);
var address = "localhost";
var fakeUdp = mocker.GetMock<UdpProvider>();
fakeUdp.Setup(s => s.Send(address, UdpProvider.PacketType.Action, It.IsAny<byte[]>())).Returns(true);
//Act
var result = mocker.Resolve<EventClientProvider>().SendAction(address, ActionType.ExecBuiltin, command);
//Assert
Assert.AreEqual(true, result);
}
[Test]
public void SendAction_Update_false()
{
//Setup
var mocker = new AutoMoqer();
var path = @"C:\Test\TV\30 Rock";
var command = String.Format("ExecBuiltIn(UpdateLibrary(video,{0}))", path);
var address = "localhost";
var fakeUdp = mocker.GetMock<UdpProvider>();
fakeUdp.Setup(s => s.Send(address, UdpProvider.PacketType.Action, It.IsAny<byte[]>())).Returns(false);
//Act
var result = mocker.Resolve<EventClientProvider>().SendAction(address, ActionType.ExecBuiltin, command);
//Assert
Assert.AreEqual(false, result);
}
}
}

@ -88,7 +88,9 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="EventClientProviderTest.cs" />
<Compile Include="CentralDispatchTest.cs" /> <Compile Include="CentralDispatchTest.cs" />
<Compile Include="XbmcProviderTest.cs" />
<Compile Include="DiskScanProviderTest.cs" /> <Compile Include="DiskScanProviderTest.cs" />
<Compile Include="EpisodeProviderTest_GetEpisodesByParseResult.cs" /> <Compile Include="EpisodeProviderTest_GetEpisodesByParseResult.cs" />
<Compile Include="DiskScanProviderTest_ImportFile.cs" /> <Compile Include="DiskScanProviderTest_ImportFile.cs" />

@ -214,7 +214,15 @@ namespace NzbDrone.Core.Test
var fakeQuality = Builder<QualityProfile>.CreateNew().Build(); var fakeQuality = Builder<QualityProfile>.CreateNew().Build();
var fakeSeries = Builder<Series>.CreateNew().With(e => e.QualityProfileId = fakeQuality.QualityProfileId).Build(); var fakeSeries = Builder<Series>.CreateNew().With(e => e.QualityProfileId = fakeQuality.QualityProfileId).Build();
var fakeEpisodes = Builder<Episode>.CreateListOfSize(10).WhereAll().Have(e => e.SeriesId = fakeSeries.SeriesId).Have(e => e.Ignored = false).WhereRandom(5).Have(e => e.EpisodeFileId = 0).Build(); var fakeEpisodes = Builder<Episode>.CreateListOfSize(10)
.WhereAll().Have(e => e.SeriesId = fakeSeries.SeriesId)
.Have(e => e.Ignored = false)
.Have(e => e.AirDate = DateTime.Today)
.WhereTheFirst(5)
.Have(e => e.EpisodeFileId = 0)
.WhereTheLast(2)
.Have(e => e.AirDate = DateTime.Today.AddDays(1))
.Build();
db.Insert(fakeSeries); db.Insert(fakeSeries);
db.Insert(fakeQuality); db.Insert(fakeQuality);
@ -226,8 +234,8 @@ namespace NzbDrone.Core.Test
//Assert //Assert
series.Should().HaveCount(1); series.Should().HaveCount(1);
Assert.AreEqual(10, series[0].EpisodeCount); Assert.AreEqual(8, series[0].EpisodeCount);
Assert.AreEqual(5, series[0].EpisodeFileCount); Assert.AreEqual(3, series[0].EpisodeFileCount);
} }
[Test] [Test]
@ -264,7 +272,12 @@ namespace NzbDrone.Core.Test
var fakeQuality = Builder<QualityProfile>.CreateNew().Build(); var fakeQuality = Builder<QualityProfile>.CreateNew().Build();
var fakeSeries = Builder<Series>.CreateNew().With(e => e.QualityProfileId = fakeQuality.QualityProfileId).Build(); var fakeSeries = Builder<Series>.CreateNew().With(e => e.QualityProfileId = fakeQuality.QualityProfileId).Build();
var fakeEpisodes = Builder<Episode>.CreateListOfSize(10).WhereAll().Have(e => e.SeriesId = fakeSeries.SeriesId).Have(e => e.Ignored = false).Build(); var fakeEpisodes = Builder<Episode>.CreateListOfSize(10)
.WhereAll()
.Have(e => e.SeriesId = fakeSeries.SeriesId)
.Have(e => e.Ignored = false)
.Have(e => e.AirDate = DateTime.Today.AddDays(-1))
.Build();
db.Insert(fakeSeries); db.Insert(fakeSeries);
db.Insert(fakeQuality); db.Insert(fakeQuality);
@ -290,9 +303,13 @@ namespace NzbDrone.Core.Test
var fakeQuality = Builder<QualityProfile>.CreateNew().Build(); var fakeQuality = Builder<QualityProfile>.CreateNew().Build();
var fakeSeries = Builder<Series>.CreateNew().With(e => e.QualityProfileId = fakeQuality.QualityProfileId).Build(); var fakeSeries = Builder<Series>.CreateNew().With(e => e.QualityProfileId = fakeQuality.QualityProfileId).Build();
var fakeEpisodes = Builder<Episode>.CreateListOfSize(10) var fakeEpisodes = Builder<Episode>.CreateListOfSize(10)
.WhereAll().Have(e => e.SeriesId = fakeSeries.SeriesId) .WhereAll()
.WhereTheFirst(5).Have(e => e.Ignored = false) .Have(e => e.SeriesId = fakeSeries.SeriesId)
.WhereTheLast(5).Have(e => e.Ignored = true) .Have(e => e.AirDate = DateTime.Today.AddDays(-1))
.WhereTheFirst(5)
.Have(e => e.Ignored = false)
.WhereTheLast(5)
.Have(e => e.Ignored = true)
.Build(); .Build();
db.Insert(fakeSeries); db.Insert(fakeSeries);

@ -0,0 +1,399 @@
// ReSharper disable RedundantUsingDirective
using System;
using System.Collections.Generic;
using AutoMoq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Model.Xbmc;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Providers.Xbmc;
using NzbDrone.Core.Repository;
using NzbDrone.Core.Repository.Quality;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test
{
[TestFixture]
// ReSharper disable InconsistentNaming
public class XbmcProviderTest : TestBase
{
[Test]
public void JsonEror_true()
{
//Setup
var mocker = new AutoMoqer();
var response = "{\"error\":{\"code\":-32601,\"message\":\"Method not found.\"},\"id\":10,\"jsonrpc\":\"2.0\"}";
//Act
var result = mocker.Resolve<XbmcProvider>().CheckForJsonError(response);
//Assert
Assert.AreEqual(true, result);
}
[Test]
public void JsonEror_false()
{
//Setup
var mocker = new AutoMoqer();
var reposnse = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"version\":3}}";
//Act
var result = mocker.Resolve<XbmcProvider>().CheckForJsonError(reposnse);
//Assert
Assert.AreEqual(false, result);
}
[TestCase(3)]
[TestCase(2)]
[TestCase(0)]
public void GetJsonVersion(int number)
{
//Setup
var mocker = new AutoMoqer();
var message = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"version\":" + number + "}}";
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny<string>()))
.Returns(message);
//Act
var result = mocker.Resolve<XbmcProvider>().GetJsonVersion("localhost:8080", "xbmc", "xbmc");
//Assert
Assert.AreEqual(number, result);
}
[Test]
public void GetJsonVersion_error()
{
//Setup
var mocker = new AutoMoqer();
var message = "{\"error\":{\"code\":-32601,\"message\":\"Method not found.\"},\"id\":10,\"jsonrpc\":\"2.0\"}";
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny<string>()))
.Returns(message);
//Act
var result = mocker.Resolve<XbmcProvider>().GetJsonVersion("localhost:8080", "xbmc", "xbmc");
//Assert
Assert.AreEqual(0, result);
}
[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 GetActivePlayers(bool audio, bool picture, bool video)
{
//Setup
var mocker = new AutoMoqer();
var message = "{\"id\":10,\"jsonrpc\":\"2.0\",\"result\":{\"audio\":"
+ audio.ToString().ToLower()
+ ",\"picture\":"
+ picture.ToString().ToLower()
+ ",\"video\":"
+ video.ToString().ToLower()
+ "}}";
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny<string>()))
.Returns(message);
//Act
var result = mocker.Resolve<XbmcProvider>().GetActivePlayers("localhost:8080", "xbmc", "xbmc");
//Assert
Assert.AreEqual(audio, result["audio"]);
Assert.AreEqual(picture, result["picture"]);
Assert.AreEqual(video, result["video"]);
}
[Test]
public void GetTvShowsJson()
{
//Setup
var mocker = new AutoMoqer();
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<HttpProvider>();
fakeHttp.Setup(s => s.PostCommand("localhost:8080", "xbmc", "xbmc", It.IsAny<string>()))
.Returns(message);
//Act
var result = mocker.Resolve<XbmcProvider>().GetTvShowsJson("localhost:8080", "xbmc", "xbmc");
//Assert
Assert.AreEqual(5, result.Count);
result.Should().Contain(s => s.ImdbNumber == 79488);
}
[Test]
public void Notify_true()
{
//Setup
var mocker = new AutoMoqer(MockBehavior.Strict);
var header = "NzbDrone Test";
var message = "Test Message!";
var fakeConfig = mocker.GetMock<ConfigProvider>();
fakeConfig.SetupGet(s => s.XbmcHosts).Returns("localhost:8080");
//var fakeUdpProvider = mocker.GetMock<EventClient>();
var fakeEventClient = mocker.GetMock<EventClientProvider>();
fakeEventClient.Setup(s => s.SendNotification(header, message, IconType.Jpeg, "NzbDrone.jpg", "localhost")).Returns(true);
//Act
mocker.Resolve<XbmcProvider>().Notify(header, message);
//Assert
mocker.VerifyAllMocks();
}
[Test]
public void SendCommand()
{
//Setup
var mocker = new AutoMoqer(MockBehavior.Strict);
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<EventClient>();
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.DownloadString(url, username, password)).Returns("Ok\n");
//Act
var result = mocker.Resolve<XbmcProvider>().SendCommand(host, command, username, username);
//Assert
mocker.VerifyAllMocks();
Assert.AreEqual("Ok\n", result);
}
[Test]
public void GetXbmcSeriesPath_true()
{
//Setup
var mocker = new AutoMoqer(MockBehavior.Strict);
var queryResult = @"<xml><record><field>smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/</field></record></xml>";
var host = "localhost:8080";
var username = "xbmc";
var password = "xbmc";
var setResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;opentag;<tag>;closetag;</tag>;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<EventClient>();
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.DownloadString(setResponseUrl, username, password)).Returns("<xml><tag>OK</xml>");
fakeHttp.Setup(s => s.DownloadString(resetResponseUrl, username, password)).Returns(@"<html>
<li>OK
</html>");
fakeHttp.Setup(s => s.DownloadString(query, username, password)).Returns(queryResult);
//Act
var result = mocker.Resolve<XbmcProvider>().GetXbmcSeriesPath(host, 79488, username, username);
//Assert
mocker.VerifyAllMocks();
Assert.AreEqual("smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/", result);
}
[Test]
public void GetXbmcSeriesPath_false()
{
//Setup
var mocker = new AutoMoqer(MockBehavior.Strict);
var queryResult = @"<xml></xml>";
var host = "localhost:8080";
var username = "xbmc";
var password = "xbmc";
var setResponseUrl = "http://localhost:8080/xbmcCmds/xbmcHttp?command=SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;opentag;<tag>;closetag;</tag>;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<EventClient>();
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.DownloadString(setResponseUrl, username, password)).Returns("<xml><tag>OK</xml>");
fakeHttp.Setup(s => s.DownloadString(resetResponseUrl, username, password)).Returns(@"<html>
<li>OK
</html>");
fakeHttp.Setup(s => s.DownloadString(query, username, password)).Returns(queryResult);
//Act
var result = mocker.Resolve<XbmcProvider>().GetXbmcSeriesPath(host, 79488, username, username);
//Assert
mocker.VerifyAllMocks();
Assert.AreEqual("", result);
}
[Test]
public void Clean()
{
//Setup
var mocker = new AutoMoqer(MockBehavior.Strict);
var fakeConfig = mocker.GetMock<ConfigProvider>();
fakeConfig.SetupGet(s => s.XbmcHosts).Returns("localhost:8080");
var fakeEventClient = mocker.GetMock<EventClientProvider>();
fakeEventClient.Setup(s => s.SendAction("localhost", ActionType.ExecBuiltin, "ExecBuiltIn(CleanLibrary(video))")).Returns(true);
//Act
mocker.Resolve<XbmcProvider>().Clean();
//Assert
mocker.VerifyAllMocks();
}
[Test]
public void UpdateWithHttp_Single()
{
//Setup
var mocker = new AutoMoqer(MockBehavior.Default);
var host = "localhost:8080";
var username = "xbmc";
var password = "xbmc";
var queryResult = @"<xml><record><field>smb://xbmc:xbmc@HOMESERVER/TV/30 Rock/</field></record></xml>";
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<Series>.CreateNew()
.With(s => s.SeriesId = 79488)
.With(s => s.Title = "30 Rock")
.Build();
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.DownloadString(queryUrl, username, password)).Returns(queryResult);
fakeHttp.Setup(s => s.DownloadString(url, username, password));
//Act
mocker.Resolve<XbmcProvider>().UpdateWithHttp(fakeSeries, host, username, password);
//Assert
mocker.VerifyAllMocks();
}
[Test]
public void UpdateWithHttp_All()
{
//Setup
var mocker = new AutoMoqer(MockBehavior.Default);
var host = "localhost:8080";
var username = "xbmc";
var password = "xbmc";
var queryResult = @"<xml></xml>";
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<Series>.CreateNew()
.With(s => s.SeriesId = 79488)
.With(s => s.Title = "30 Rock")
.Build();
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.DownloadString(queryUrl, username, password)).Returns(queryResult);
fakeHttp.Setup(s => s.DownloadString(url, username, password));
//Act
mocker.Resolve<XbmcProvider>().UpdateWithHttp(fakeSeries, host, username, password);
//Assert
mocker.VerifyAllMocks();
}
[Test]
public void UpdateWithJson_Single()
{
//Setup
var mocker = new AutoMoqer();
var host = "localhost:8080";
var username = "xbmc";
var password = "xbmc";
var serializedQuery = "{\"jsonrpc\":\"2.0\",\"method\":\"VideoLibrary.GetTvShows\",\"params\":{\"fields\":[\"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<Series>.CreateNew()
.With(s => s.SeriesId = 79488)
.With(s => s.Title = "30 Rock")
.Build();
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.PostCommand(host, username, password, serializedQuery))
.Returns(tvshows);
var fakeEventClient = mocker.GetMock<EventClientProvider>();
fakeEventClient.Setup(s => s.SendAction("localhost", ActionType.ExecBuiltin, "ExecBuiltIn(UpdateLibrary(video,smb://HOMESERVER/TV/30 Rock/))"));
//Act
mocker.Resolve<XbmcProvider>().UpdateWithJson(fakeSeries, host, username, password);
//Assert
mocker.VerifyAllMocks();
}
[Test]
public void UpdateWithJson_All()
{
//Setup
var mocker = new AutoMoqer();
var host = "localhost:8080";
var username = "xbmc";
var password = "xbmc";
var serializedQuery = "{\"jsonrpc\":\"2.0\",\"method\":\"VideoLibrary.GetTvShows\",\"params\":{\"fields\":[\"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<Series>.CreateNew()
.With(s => s.SeriesId = 79488)
.With(s => s.Title = "30 Rock")
.Build();
var fakeHttp = mocker.GetMock<HttpProvider>();
fakeHttp.Setup(s => s.PostCommand(host, username, password, serializedQuery))
.Returns(tvshows);
var fakeEventClient = mocker.GetMock<EventClientProvider>();
fakeEventClient.Setup(s => s.SendAction("localhost", ActionType.ExecBuiltin, "ExecBuiltIn(UpdateLibrary(video))"));
//Act
mocker.Resolve<XbmcProvider>().UpdateWithJson(fakeSeries, host, username, password);
//Assert
mocker.VerifyAllMocks();
}
}
}

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public enum ActionType
{
ExecBuiltin = 0x01,
Button = 0x02
}
}

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public class ActivePlayersResult
{
public string Id { get; set; }
public string JsonRpc { get; set; }
public Dictionary<string, bool> Result { get; set; }
}
}

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public class Command
{
public string jsonrpc
{
get { return "2.0"; }
}
public string method { get; set; }
public Params @params { get; set; }
public long id { get; set; }
}
}

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public class ErrorResult
{
public string Id { get; set; }
public string JsonRpc { get; set; }
public Dictionary<string, string> Error { get; set; }
}
}

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public enum IconType
{
None = 0x00,
Jpeg = 0x01,
Png = 0x02,
Gif = 0x03
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public class Params
{
public string[] fields { get; set; }
}
}

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public class TvShow
{
public int TvShowId { get; set; }
public string Label { get; set; }
public int ImdbNumber { get; set; }
public string File { get; set; }
}
}

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public class TvShowResult
{
public string Id { get; set; }
public string JsonRpc { get; set; }
public Dictionary<string, List<TvShow>> Result { get; set; }
}
}

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Model.Xbmc
{
public class VersionResult
{
public string Id { get; set; }
public string JsonRpc { get; set; }
public Dictionary<string, int> Result { get; set; }
}
}

@ -190,6 +190,17 @@
<Compile Include="Model\LanguageType.cs" /> <Compile Include="Model\LanguageType.cs" />
<Compile Include="Model\Quality.cs" /> <Compile Include="Model\Quality.cs" />
<Compile Include="Model\SabnzbdInfoModel.cs" /> <Compile Include="Model\SabnzbdInfoModel.cs" />
<Compile Include="Model\Xbmc\ActionType.cs" />
<Compile Include="Model\Xbmc\ActivePlayersResult.cs" />
<Compile Include="Model\Xbmc\ErrorResult.cs" />
<Compile Include="Model\Xbmc\IconType.cs" />
<Compile Include="Providers\Core\UdpProvider.cs" />
<Compile Include="Providers\Xbmc\ResourceManager.cs" />
<Compile Include="Model\Xbmc\TvShowResult.cs" />
<Compile Include="Model\Xbmc\Params.cs" />
<Compile Include="Model\Xbmc\Command.cs" />
<Compile Include="Model\Xbmc\TvShow.cs" />
<Compile Include="Model\Xbmc\VersionResult.cs" />
<Compile Include="Providers\DiskScanProvider.cs" /> <Compile Include="Providers\DiskScanProvider.cs" />
<Compile Include="Providers\DownloadProvider.cs" /> <Compile Include="Providers\DownloadProvider.cs" />
<Compile Include="Providers\ExternalNotification\ExternalNotificationProviderBase.cs" /> <Compile Include="Providers\ExternalNotification\ExternalNotificationProviderBase.cs" />
@ -212,6 +223,7 @@
<Compile Include="Providers\Jobs\RssSyncJob.cs" /> <Compile Include="Providers\Jobs\RssSyncJob.cs" />
<Compile Include="Providers\Jobs\UpdateInfoJob.cs" /> <Compile Include="Providers\Jobs\UpdateInfoJob.cs" />
<Compile Include="Providers\SceneMappingProvider.cs" /> <Compile Include="Providers\SceneMappingProvider.cs" />
<Compile Include="Providers\Xbmc\EventClientProvider.cs" />
<Compile Include="Repository\ExternalNotificationSetting.cs" /> <Compile Include="Repository\ExternalNotificationSetting.cs" />
<Compile Include="Repository\JobDefinition.cs" /> <Compile Include="Repository\JobDefinition.cs" />
<Compile Include="Repository\IndexerDefinition.cs" /> <Compile Include="Repository\IndexerDefinition.cs" />
@ -291,6 +303,9 @@
<ItemGroup> <ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" /> <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="NzbDrone.jpg" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent> <PostBuildEvent>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -120,13 +120,6 @@ namespace NzbDrone.Core.Providers.Core
set { SetValue("DownloadPropers", value); } set { SetValue("DownloadPropers", value); }
} }
public virtual Int32 Retention
{
get { return GetValueInt("Retention"); }
set { SetValue("Retention", value); }
}
public virtual String SabHost public virtual String SabHost
{ {
get { return GetValue("SabHost", "localhost"); } get { return GetValue("SabHost", "localhost"); }
@ -259,6 +252,59 @@ namespace NzbDrone.Core.Providers.Core
set { SetValue("DefaultQualityProfile", value); } set { SetValue("DefaultQualityProfile", value); }
} }
public virtual Boolean XbmcEnabled
{
get { return GetValueBoolean("XbmcEnabled"); }
set { SetValue("XbmcEnabled", value); }
}
public virtual Boolean XbmcNotifyOnGrab
{
get { return GetValueBoolean("XbmcNotifyOnGrab"); }
set { SetValue("XbmcNotifyOnGrab", value); }
}
public virtual Boolean XbmcNotifyOnDownload
{
get { return GetValueBoolean("XbmcNotifyOnDownload"); }
set { SetValue("XbmcNotifyOnDownload", value); }
}
public virtual Boolean XbmcUpdateLibrary
{
get { return GetValueBoolean("XbmcUpdateLibrary"); }
set { SetValue("XbmcUpdateLibrary", value); }
}
public virtual Boolean XbmcCleanLibrary
{
get { return GetValueBoolean("XbmcCleanLibrary"); }
set { SetValue("XbmcCleanLibrary", value); }
}
public virtual string XbmcHosts
{
get { return GetValue("XbmcHosts", "localhost:8080"); }
set { SetValue("XbmcHosts", value); }
}
public virtual string XbmcUsername
{
get { return GetValue("XbmcUsername", "xbmc"); }
set { SetValue("XbmcUsername", value); }
}
public virtual string XbmcPassword
{
get { return GetValue("XbmcPassword", String.Empty); }
set { SetValue("XbmcPassword", value); }
}
private string GetValue(string key) private string GetValue(string key)
{ {
return GetValue(key, String.Empty); return GetValue(key, String.Empty);

@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text;
using NLog; using NLog;
namespace NzbDrone.Core.Providers.Core namespace NzbDrone.Core.Providers.Core
@ -64,5 +65,33 @@ namespace NzbDrone.Core.Providers.Core
return false; return false;
} }
} }
public virtual string PostCommand(string address, string username, string password, string command)
{
address += "/jsonrpc";
byte[] byteArray = Encoding.ASCII.GetBytes(command);
var request = WebRequest.Create(address);
request.Method = "POST";
request.Credentials = new NetworkCredential(username, password);
request.ContentLength = byteArray.Length;
request.ContentType = "application/x-www-form-urlencoded";
var dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
var response = request.GetResponse();
dataStream = response.GetResponseStream();
var reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
reader.Close();
dataStream.Close();
response.Close();
return responseFromServer.Replace("&nbsp;", " ");
}
} }
} }

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Ninject;
namespace NzbDrone.Core.Providers.Core
{
public class UdpProvider
{
[Inject]
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] = (byte)(((ushort)PacketType.Blob & 0xff00) >> 8);
header[7] = (byte)((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)
{
Console.WriteLine(exc);
return null;
}
}
private void Disconnect(Socket socket)
{
try
{
if (socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}
catch
{
}
}
}
}

@ -1,4 +1,5 @@
using NLog; using System;
using NLog;
using NzbDrone.Core.Model; using NzbDrone.Core.Model;
using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Repository; using NzbDrone.Core.Repository;
@ -37,10 +38,19 @@ namespace NzbDrone.Core.Providers.ExternalNotification
OnGrab(message); OnGrab(message);
else if (type == ExternalNotificationType.Download) else if (type == ExternalNotificationType.Download)
OnDownload(message, seriesId); {
throw new NotImplementedException();
var series = new Series();
OnDownload(message, series);
}
else if (type == ExternalNotificationType.Rename) else if (type == ExternalNotificationType.Rename)
OnRename(message, seriesId); {
throw new NotImplementedException();
var series = new Series();
OnRename(message, series);
}
} }
/// <summary> /// <summary>
@ -53,14 +63,14 @@ namespace NzbDrone.Core.Providers.ExternalNotification
/// Performs the on download action /// Performs the on download action
/// </summary> /// </summary>
/// <param name = "message">The message to send to the receiver</param> /// <param name = "message">The message to send to the receiver</param>
/// <param name = "seriesId">The Series ID for the new download</param> /// <param name = "series">The Series for the new download</param>
public abstract void OnDownload(string message, int seriesId); public abstract void OnDownload(string message, Series series);
/// <summary> /// <summary>
/// Performs the on rename action /// Performs the on rename action
/// </summary> /// </summary>
/// <param name = "message">The message to send to the receiver</param> /// <param name = "message">The message to send to the receiver</param>
/// <param name = "seriesId">The Series ID for the new download</param> /// <param name = "series">The Series for the new download</param>
public abstract void OnRename(string message, int seriesId); public abstract void OnRename(string message, Series series);
} }
} }

@ -1,5 +1,6 @@
using System; using System;
using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Repository;
namespace NzbDrone.Core.Providers.ExternalNotification namespace NzbDrone.Core.Providers.ExternalNotification
{ {
@ -37,7 +38,7 @@ namespace NzbDrone.Core.Providers.ExternalNotification
_logger.Trace("XBMC Notifier is not enabled"); _logger.Trace("XBMC Notifier is not enabled");
} }
public override void OnDownload(string message, int seriesId) public override void OnDownload(string message, Series series)
{ {
const string header = "NzbDrone [TV] - Downloaded"; const string header = "NzbDrone [TV] - Downloaded";
@ -52,7 +53,7 @@ namespace NzbDrone.Core.Providers.ExternalNotification
if (Convert.ToBoolean(_configProvider.GetValue("XbmcUpdateOnDownload", false))) if (Convert.ToBoolean(_configProvider.GetValue("XbmcUpdateOnDownload", false)))
{ {
_logger.Trace("Sending Update Request to XBMC"); _logger.Trace("Sending Update Request to XBMC");
_xbmcProvider.Update(seriesId); _xbmcProvider.Update(series);
} }
if (Convert.ToBoolean(_configProvider.GetValue("XbmcCleanOnDownload", false))) if (Convert.ToBoolean(_configProvider.GetValue("XbmcCleanOnDownload", false)))
@ -65,7 +66,7 @@ namespace NzbDrone.Core.Providers.ExternalNotification
_logger.Trace("XBMC Notifier is not enabled"); _logger.Trace("XBMC Notifier is not enabled");
} }
public override void OnRename(string message, int seriesId) public override void OnRename(string message, Series series)
{ {
const string header = "NzbDrone [TV] - Renamed"; const string header = "NzbDrone [TV] - Renamed";
@ -78,7 +79,7 @@ namespace NzbDrone.Core.Providers.ExternalNotification
if (Convert.ToBoolean(_configProvider.GetValue("XbmcUpdateOnRename", false))) if (Convert.ToBoolean(_configProvider.GetValue("XbmcUpdateOnRename", false)))
{ {
_logger.Trace("Sending Update Request to XBMC"); _logger.Trace("Sending Update Request to XBMC");
_xbmcProvider.Update(seriesId); _xbmcProvider.Update(series);
} }
if (Convert.ToBoolean(_configProvider.GetValue("XbmcCleanOnRename", false))) if (Convert.ToBoolean(_configProvider.GetValue("XbmcCleanOnRename", false)))

@ -47,8 +47,8 @@ namespace NzbDrone.Core.Providers
var series = _database var series = _database
.Fetch<Series, QualityProfile>(@"SELECT Series.SeriesId, Series.Title, Series.CleanTitle, Series.Status, Series.Overview, Series.AirsDayOfWeek,Series.AirTimes, .Fetch<Series, QualityProfile>(@"SELECT Series.SeriesId, Series.Title, Series.CleanTitle, Series.Status, Series.Overview, Series.AirsDayOfWeek,Series.AirTimes,
Series.Language, Series.Path, Series.Monitored, Series.QualityProfileId, Series.SeasonFolder, Series.Language, Series.Path, Series.Monitored, Series.QualityProfileId, Series.SeasonFolder,
SUM(CASE WHEN Ignored = 0 THEN 1 ELSE 0 END) AS EpisodeCount, SUM(CASE WHEN Ignored = 0 AND Airdate <= @0 THEN 1 ELSE 0 END) AS EpisodeCount,
SUM(CASE WHEN Episodes.Ignored = 0 AND Episodes.EpisodeFileId > 0 THEN 1 ELSE 0 END) as EpisodeFileCount, SUM(CASE WHEN Episodes.Ignored = 0 AND Episodes.EpisodeFileId > 0 AND Episodes.AirDate <= @0 THEN 1 ELSE 0 END) as EpisodeFileCount,
MAX(Episodes.SeasonNumber) as SeasonCount, MAX(Episodes.SeasonNumber) as SeasonCount,
QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed
FROM Series FROM Series
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Providers
LEFT JOIN Episodes ON Series.SeriesId = Episodes.SeriesId LEFT JOIN Episodes ON Series.SeriesId = Episodes.SeriesId
GROUP BY Series.SeriesId, Series.Title, Series.CleanTitle, Series.Status, Series.Overview, Series.AirsDayOfWeek,Series.AirTimes, GROUP BY Series.SeriesId, Series.Title, Series.CleanTitle, Series.Status, Series.Overview, Series.AirsDayOfWeek,Series.AirTimes,
Series.Language, Series.Path, Series.Monitored, Series.QualityProfileId, Series.SeasonFolder, Series.Language, Series.Path, Series.Monitored, Series.QualityProfileId, Series.SeasonFolder,
QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed"); QualityProfiles.QualityProfileId, QualityProfiles.Name, QualityProfiles.Cutoff, QualityProfiles.SonicAllowed", DateTime.Today);
return series; return series;
} }

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;
using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Model.Xbmc;
namespace NzbDrone.Core.Providers.Xbmc
{
public class EventClientProvider
{
private readonly UdpProvider _udpProvider;
[Inject]
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++] = (byte)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);
}
}
}

@ -0,0 +1,55 @@
namespace NzbDrone.Core.Providers.Xbmc
{
public class ResourceManager
{
public static System.Drawing.Icon GetIcon(string Name)
{
System.IO.Stream stm = typeof(ResourceManager).Assembly.GetManifestResourceStream(string.Format("NzbDrone.Core.{0}.ico", Name));
if (stm == null) return null;
return new System.Drawing.Icon(stm);
}
public static byte[] GetRawData(string Name)
{
byte[] data;
using (System.IO.Stream stm = typeof(ResourceManager).Assembly.GetManifestResourceStream(string.Format("NzbDrone.Core.{0}.ico", Name)))
{
if (stm == null) return null;
data = new byte[stm.Length];
stm.Read(data, 0, data.Length);
}
return data;
}
public static byte[] GetRawLogo(string Name)
{
byte[] data;
using (System.IO.Stream stm = typeof(ResourceManager).Assembly.GetManifestResourceStream(string.Format("NzbDrone.Core.{0}", Name)))
{
if (stm == null) return null;
data = new byte[stm.Length];
stm.Read(data, 0, data.Length);
}
return data;
}
public static System.Drawing.Bitmap GetIconAsImage(string Name)
{
System.IO.Stream stm = typeof(ResourceManager).Assembly.GetManifestResourceStream(string.Format("{0}.Icons.{1}.ico", typeof(ResourceManager).Namespace, Name));
if (stm == null) return null;
System.Drawing.Bitmap bmp;
using (System.Drawing.Icon ico = new System.Drawing.Icon(stm))
{
bmp = new System.Drawing.Bitmap(ico.Width, ico.Height);
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp))
{
g.DrawIcon(ico, 0, 0);
}
}
return bmp;
}
}
}

@ -1,10 +1,15 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Web.Script.Serialization;
using System.Xml.Linq; using System.Xml.Linq;
using Ninject; using Ninject;
using NLog; using NLog;
using NzbDrone.Core.Model.Xbmc;
using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Core;
using NzbDrone.Core.Providers.Xbmc;
using NzbDrone.Core.Repository;
namespace NzbDrone.Core.Providers namespace NzbDrone.Core.Providers
{ {
@ -13,72 +18,134 @@ namespace NzbDrone.Core.Providers
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly ConfigProvider _configProvider; private readonly ConfigProvider _configProvider;
private readonly HttpProvider _httpProvider; private readonly HttpProvider _httpProvider;
private readonly EventClientProvider _eventClientProvider;
[Inject] [Inject]
public XbmcProvider(ConfigProvider configProvider, HttpProvider httpProvider) public XbmcProvider(ConfigProvider configProvider, HttpProvider httpProvider, EventClientProvider eventClientProvider)
{ {
_configProvider = configProvider; _configProvider = configProvider;
_httpProvider = httpProvider; _httpProvider = httpProvider;
_eventClientProvider = eventClientProvider;
} }
public virtual void Notify(string header, string message) public virtual void Notify(string header, string message)
{ {
//Get time in seconds and convert to ms //Always use EventServer, until Json has real support for it
var time = Convert.ToInt32(_configProvider.GetValue("XbmcDisplayTime", "3")) * 1000; foreach (var host in _configProvider.XbmcHosts.Split(','))
var command = String.Format("ExecBuiltIn(Notification({0},{1},{2}))", header, message, time); {
Logger.Trace("Sending Notifcation to XBMC Host: {0}", host);
_eventClientProvider.SendNotification(header, message, IconType.Jpeg, "NzbDrone.jpg", GetHostWithoutPort(host));
}
}
public XbmcProvider()
{
}
public virtual void Update(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 = _configProvider.XbmcUsername;
var password = _configProvider.XbmcPassword;
if (Convert.ToBoolean(_configProvider.GetValue("XbmcNotificationImage", false))) foreach (var host in _configProvider.XbmcHosts.Split(','))
{ {
//Todo: Get the actual port that NzbDrone is running on... Logger.Trace("Determining version of XBMC Host: {0}", host);
var serverInfo = String.Format("http://{0}:{1}", Environment.MachineName, "8989"); var version = GetJsonVersion(host, username, password);
var imageUrl = String.Format("{0}/Content/XbmcNotification.png", serverInfo); //If Dharma
command = String.Format("ExecBuiltIn(Notification({0},{1},{2}, {3}))", header, message, time, imageUrl); if (version == 2)
UpdateWithHttp(series, host, username, password);
//If Eden or newer (attempting to make it future compatible)
else if (version >= 3)
UpdateWithJson(series, password, host, username);
} }
}
foreach (var host in _configProvider.GetValue("XbmcHosts", "localhost:80").Split(',')) public virtual bool UpdateWithJson(Series series, string host, string username, string password)
{
try
{ {
Logger.Trace("Sending Notifcation to XBMC Host: {0}", host); //Use Json!
SendCommand(host, command); var xbmcShows = GetTvShowsJson(host, username, password);
var path = xbmcShows.Where(s => s.ImdbNumber == series.SeriesId || s.Label == series.Title).FirstOrDefault();
var hostOnly = GetHostWithoutPort(host);
if (path != null)
{
Logger.Trace("Updating series [{0}] on XBMC host: {1}", series.Title, host);
var command = String.Format("ExecBuiltIn(UpdateLibrary(video,{0}))", path.File);
_eventClientProvider.SendAction(hostOnly, ActionType.ExecBuiltin, command);
}
else
{
Logger.Trace("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", series.Title, host);
var command = String.Format("ExecBuiltIn(UpdateLibrary(video))");
_eventClientProvider.SendAction(hostOnly, ActionType.ExecBuiltin, command);
}
} }
catch (Exception ex)
{
Logger.DebugException(ex.Message, ex);
return false;
}
return true;
} }
public virtual void Update(int seriesId) public virtual bool UpdateWithHttp(Series series, string host, string username, string password)
{ {
foreach (var host in _configProvider.GetValue("XbmcHosts", "localhost:80").Split(',')) try
{ {
Logger.Trace("Sending Update DB Request to XBMC Host: {0}", host); Logger.Trace("Sending Update DB Request to XBMC Host: {0}", host);
var xbmcSeriesPath = GetXbmcSeriesPath(host, seriesId); var xbmcSeriesPath = GetXbmcSeriesPath(host, series.SeriesId, username, password);
//If the path is not found & the user wants to update the entire library, do it now. //If the path is found update it, else update the whole library
if (String.IsNullOrEmpty(xbmcSeriesPath) && if (!String.IsNullOrEmpty(xbmcSeriesPath))
Convert.ToBoolean(_configProvider.GetValue("XbmcFullUpdate", false))) {
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 //Update the entire library
Logger.Trace("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", seriesId, host); Logger.Trace("Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library", series.Title, host);
SendCommand(host, "ExecBuiltIn(UpdateLibrary(video))"); SendCommand(host, "ExecBuiltIn(UpdateLibrary(video))", username, password);
return;
} }
}
var command = String.Format("ExecBuiltIn(UpdateLibrary(video,{0}))", xbmcSeriesPath); catch (Exception ex)
SendCommand(host, command); {
Logger.DebugException(ex.Message, ex);
return false;
} }
return true;
} }
public virtual void Clean() public virtual void Clean()
{ {
foreach (var host in _configProvider.GetValue("XbmcHosts", "localhost:80").Split(',')) //Use EventServer, once Dharma is extinct use Json?
foreach (var host in _configProvider.XbmcHosts.Split(','))
{ {
Logger.Trace("Sending DB Clean Request to XBMC Host: {0}", host); Logger.Trace("Sending DB Clean Request to XBMC Host: {0}", host);
var command = String.Format("ExecBuiltIn(CleanLibrary(video))"); var command = "ExecBuiltIn(CleanLibrary(video))";
SendCommand(host, command); _eventClientProvider.SendAction(GetHostWithoutPort(host), ActionType.ExecBuiltin, command);
} }
} }
private string SendCommand(string host, string command) public virtual string SendCommand(string host, string command, string username, string password)
{ {
var username = _configProvider.GetValue("XbmcUsername", String.Empty);
var password = _configProvider.GetValue("XbmcPassword", String.Empty);
var url = String.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", host, command); var url = String.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", host, command);
if (!String.IsNullOrEmpty(username)) if (!String.IsNullOrEmpty(username))
@ -89,7 +156,7 @@ namespace NzbDrone.Core.Providers
return _httpProvider.DownloadString(url); return _httpProvider.DownloadString(url);
} }
private string GetXbmcSeriesPath(string host, int seriesId) public virtual string GetXbmcSeriesPath(string host, int seriesId, string username, string password)
{ {
var query = var query =
String.Format( String.Format(
@ -97,13 +164,13 @@ namespace NzbDrone.Core.Providers
seriesId); seriesId);
var command = String.Format("QueryVideoDatabase({0})", query); var command = String.Format("QueryVideoDatabase({0})", query);
var setResponseCommand = const string setResponseCommand =
"SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;opentag;<tag>;closetag;</tag>;closefinaltag;false)"; "SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;opentag;<tag>;closetag;</tag>;closefinaltag;false)";
var resetResponseCommand = "SetResponseFormat()"; const string resetResponseCommand = "SetResponseFormat()";
SendCommand(host, setResponseCommand); SendCommand(host, setResponseCommand, username, password);
var response = SendCommand(host, command); var response = SendCommand(host, command, username, password);
SendCommand(host, resetResponseCommand); SendCommand(host, resetResponseCommand, username, password);
if (String.IsNullOrEmpty(response)) if (String.IsNullOrEmpty(response))
return String.Empty; return String.Empty;
@ -121,5 +188,109 @@ namespace NzbDrone.Core.Providers
return field.Value; return field.Value;
} }
public virtual int GetJsonVersion(string host, string username, string password)
{
//2 = Dharma
//3 = Eden/Nightly (as of July 2011)
var version = 0;
try
{
var command = new Command { id = 10, method = "JSONRPC.Version" };
var serializer = new JavaScriptSerializer();
var serialized = serializer.Serialize(command);
var response = _httpProvider.PostCommand(host, username, password, serialized);
if (CheckForJsonError(response))
return version;
var result = serializer.Deserialize<VersionResult>(response);
result.Result.TryGetValue("version", out version);
}
catch (Exception ex)
{
Logger.DebugException(ex.Message, ex);
}
return version;
}
public virtual Dictionary<string, bool> GetActivePlayers(string host, string username, string password)
{
//2 = Dharma
//3 = Eden/Nightly (as of July 2011)
try
{
var command = new Command { id = 10, method = "Player.GetActivePlayers" };
var serializer = new JavaScriptSerializer();
var serialized = serializer.Serialize(command);
var response = _httpProvider.PostCommand(host, username, password, serialized);
if (CheckForJsonError(response))
return null;
var result = serializer.Deserialize<ActivePlayersResult>(response);
return result.Result;
}
catch (Exception ex)
{
Logger.DebugException(ex.Message, ex);
}
return null;
}
public virtual List<TvShow> GetTvShowsJson(string host, string username, string password)
{
try
{
var fields = new string[] { "file", "imdbnumber" };
var xbmcParams = new Params { fields = fields };
var command = new Command { id = 10, method = "VideoLibrary.GetTvShows", @params = xbmcParams };
var serializer = new JavaScriptSerializer();
var serialized = serializer.Serialize(command);
var response = _httpProvider.PostCommand(host, username, password, serialized);
if (CheckForJsonError(response))
return null;
var result = serializer.Deserialize<TvShowResult>(response);
var shows = result.Result["tvshows"];
return shows;
}
catch (Exception ex)
{
Logger.DebugException(ex.Message, ex);
}
return null;
}
public virtual bool CheckForJsonError(string response)
{
if (response.StartsWith("{\"error\""))
{
var serializer = new JavaScriptSerializer();
var error = serializer.Deserialize<ErrorResult>(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;
}
private string GetHostWithoutPort(string address)
{
return address.Split(':')[0];
}
} }
} }

@ -243,10 +243,10 @@ th, td, caption
{ {
padding: 4px 10px 4px 5px; padding: 4px 10px 4px 5px;
} }
tbody tr:nth-child(even) td, tbody tr.even td /*tbody tr:nth-child(even) td, tbody tr.even td
{ {
background: #e5ecf9; background: #e5ecf9;
} }*/
tfoot tfoot
{ {
font-style: italic; font-style: italic;

@ -140,20 +140,14 @@ namespace NzbDrone.Web.Controllers
{ {
var model = new NotificationSettingsModel var model = new NotificationSettingsModel
{ {
XbmcEnabled = Convert.ToBoolean(_configProvider.GetValue("XbmcEnabled", false)), XbmcEnabled = _configProvider.XbmcEnabled,
XbmcNotifyOnGrab = Convert.ToBoolean(_configProvider.GetValue("XbmcNotifyOnGrab", false)), XbmcNotifyOnGrab = _configProvider.XbmcNotifyOnGrab,
XbmcNotifyOnDownload = Convert.ToBoolean(_configProvider.GetValue("XbmcNotifyOnDownload", false)), XbmcNotifyOnDownload = _configProvider.XbmcNotifyOnDownload,
XbmcNotifyOnRename = Convert.ToBoolean(_configProvider.GetValue("XbmcNotifyOnRename", false)), XbmcUpdateLibrary = _configProvider.XbmcUpdateLibrary,
XbmcNotificationImage = Convert.ToBoolean(_configProvider.GetValue("XbmcNotificationImage", false)), XbmcCleanLibrary = _configProvider.XbmcCleanLibrary,
XbmcDisplayTime = Convert.ToInt32(_configProvider.GetValue("XbmcDisplayTime", 3)), XbmcHosts = _configProvider.XbmcHosts,
XbmcUpdateOnDownload = Convert.ToBoolean(_configProvider.GetValue("XbmcUpdateOnDownload ", false)), XbmcUsername = _configProvider.XbmcUsername,
XbmcUpdateOnRename = Convert.ToBoolean(_configProvider.GetValue("XbmcUpdateOnRename", false)), XbmcPassword = _configProvider.XbmcPassword
XbmcFullUpdate = Convert.ToBoolean(_configProvider.GetValue("XbmcFullUpdate", false)),
XbmcCleanOnDownload = Convert.ToBoolean(_configProvider.GetValue("XbmcCleanOnDownload", false)),
XbmcCleanOnRename = Convert.ToBoolean(_configProvider.GetValue("XbmcCleanOnRename", false)),
XbmcHosts = _configProvider.GetValue("XbmcHosts", "localhost:80"),
XbmcUsername = _configProvider.GetValue("XbmcUsername", String.Empty),
XbmcPassword = _configProvider.GetValue("XbmcPassword", String.Empty)
}; };
return View(model); return View(model);
@ -401,20 +395,14 @@ namespace NzbDrone.Web.Controllers
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
_configProvider.SetValue("XbmcEnabled", data.XbmcEnabled.ToString()); _configProvider.XbmcEnabled = data.XbmcEnabled;
_configProvider.SetValue("XbmcNotifyOnGrab", data.XbmcNotifyOnGrab.ToString()); _configProvider.XbmcNotifyOnGrab = data.XbmcNotifyOnGrab;
_configProvider.SetValue("XbmcNotifyOnDownload", data.XbmcNotifyOnDownload.ToString()); _configProvider.XbmcNotifyOnDownload = data.XbmcNotifyOnDownload;
_configProvider.SetValue("XbmcNotifyOnRename", data.XbmcNotifyOnRename.ToString()); _configProvider.XbmcUpdateLibrary = data.XbmcUpdateLibrary;
_configProvider.SetValue("XbmcNotificationImage", data.XbmcNotificationImage.ToString()); _configProvider.XbmcCleanLibrary = data.XbmcCleanLibrary;
_configProvider.SetValue("XbmcDisplayTime", data.XbmcDisplayTime.ToString()); _configProvider.XbmcHosts = data.XbmcHosts;
_configProvider.SetValue("XbmcUpdateOnDownload", data.XbmcUpdateOnDownload.ToString()); _configProvider.XbmcUsername = data.XbmcUsername;
_configProvider.SetValue("XbmcUpdateOnRename", data.XbmcUpdateOnRename.ToString()); _configProvider.XbmcPassword = data.XbmcPassword;
_configProvider.SetValue("XbmcFullUpdate", data.XbmcFullUpdate.ToString());
_configProvider.SetValue("XbmcCleanOnDownload", data.XbmcCleanOnDownload.ToString());
_configProvider.SetValue("XbmcCleanOnRename", data.XbmcCleanOnRename.ToString());
_configProvider.SetValue("XbmcHosts", data.XbmcHosts);
_configProvider.SetValue("XbmcUsername", data.XbmcUsername);
_configProvider.SetValue("XbmcPassword", data.XbmcPassword);
basicNotification.Title = SETTINGS_SAVED; basicNotification.Title = SETTINGS_SAVED;
_notificationProvider.Register(basicNotification); _notificationProvider.Register(basicNotification);

@ -17,43 +17,17 @@ namespace NzbDrone.Web.Models
[Description("Send notification when episode is downloaded?")] [Description("Send notification when episode is downloaded?")]
public bool XbmcNotifyOnDownload { get; set; } public bool XbmcNotifyOnDownload { get; set; }
[DisplayName("Notify on Rename")] [DisplayName("Update on Download and Rename")]
[Description("Send notification when episode is renamed?")] [Description("Update XBMC library after episode is downloaded or renamed?")]
public bool XbmcNotifyOnRename { get; set; } public bool XbmcUpdateLibrary { get; set; }
[DisplayName("Image with Notification")] [DisplayName("Clean on Download/Rename")]
[Description("Display NzbDrone image on notifications?")] [Description("Clean XBMC library after an episode is downloaded or renamed?")]
public bool XbmcNotificationImage { get; set; } public bool XbmcCleanLibrary { get; set; }
[Required]
[Range(3, 10, ErrorMessage = "Must be between 3 and 10 seconds")]
[DisplayName("Display Time")]
[Description("How long the notification should be displayed")]
public int XbmcDisplayTime { get; set; }
[DisplayName("Update on Download")]
[Description("Update XBMC library after episode download?")]
public bool XbmcUpdateOnDownload { get; set; }
[DisplayName("Update on Rename")]
[Description("Update XBMC library after episode is renamed?")]
public bool XbmcUpdateOnRename { get; set; }
[DisplayName("Full Update")]
[Description("Perform a full update is series update fails?")]
public bool XbmcFullUpdate { get; set; }
[DisplayName("Clean on Download")]
[Description("Clean XBMC library after episode download?")]
public bool XbmcCleanOnDownload { get; set; }
[DisplayName("Clean on Rename")]
[Description("Clean XBMC library after episode is renamed?")]
public bool XbmcCleanOnRename { get; set; }
[DataType(DataType.Text)] [DataType(DataType.Text)]
[DisplayName("Hosts")] [DisplayName("Hosts")]
[Description("XBMC hosts with port, comma separ")] [Description("XBMC hosts with port, comma separated")]
[DisplayFormat(ConvertEmptyStringToNull = false)] [DisplayFormat(ConvertEmptyStringToNull = false)]
public string XbmcHosts { get; set; } public string XbmcHosts { get; set; }

@ -40,6 +40,11 @@ NZBDrone
height: 100%; height: 100%;
text-align: center; text-align: center;
} }
.t-grid .t-alt
{
background: #E5ECF9;
}
</style> </style>
@section ActionMenu{ @section ActionMenu{
@{Html.RenderPartial("SubMenu");} @{Html.RenderPartial("SubMenu");}

@ -1,10 +1,6 @@
@using NzbDrone.Web.Helpers @using NzbDrone.Web.Helpers
@model NzbDrone.Web.Models.IndexerSettingsModel @model NzbDrone.Web.Models.IndexerSettingsModel
@section HeaderContent{ @section HeaderContent{
<link rel="stylesheet" type="text/css" href="../../Content/Settings.css" /> <link rel="stylesheet" type="text/css" href="../../Content/Settings.css" />

@ -66,45 +66,15 @@
</label> </label>
@Html.CheckBoxFor(m => m.XbmcNotifyOnDownload, new { @class = "inputClass checkClass" }) @Html.CheckBoxFor(m => m.XbmcNotifyOnDownload, new { @class = "inputClass checkClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcNotifyOnRename) <label class="labelClass">@Html.LabelFor(m => m.XbmcUpdateLibrary)
<span class="small">@Html.DescriptionFor(m => m.XbmcNotifyOnRename)</span> <span class="small">@Html.DescriptionFor(m => m.XbmcUpdateLibrary)</span>
</label> </label>
@Html.CheckBoxFor(m => m.XbmcNotifyOnRename, new { @class = "inputClass checkClass" }) @Html.CheckBoxFor(m => m.XbmcUpdateLibrary, new { @class = "inputClass checkClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcNotificationImage) <label class="labelClass">@Html.LabelFor(m => m.XbmcCleanLibrary)
<span class="small">@Html.DescriptionFor(m => m.XbmcNotificationImage)</span> <span class="small">@Html.DescriptionFor(m => m.XbmcCleanLibrary)</span>
</label> </label>
@Html.CheckBoxFor(m => m.XbmcNotificationImage, new { @class = "inputClass checkClass" }) @Html.CheckBoxFor(m => m.XbmcCleanLibrary, new { @class = "inputClass checkClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcDisplayTime)
<span class="small">@Html.DescriptionFor(m => m.XbmcDisplayTime)</span>
</label>
@Html.TextBoxFor(m => m.XbmcDisplayTime, new { @class = "inputClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcUpdateOnDownload)
<span class="small">@Html.DescriptionFor(m => m.XbmcUpdateOnDownload)</span>
</label>
@Html.CheckBoxFor(m => m.XbmcUpdateOnDownload, new { @class = "inputClass checkClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcUpdateOnRename)
<span class="small">@Html.DescriptionFor(m => m.XbmcUpdateOnRename)</span>
</label>
@Html.CheckBoxFor(m => m.XbmcUpdateOnRename, new { @class = "inputClass checkClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcFullUpdate)
<span class="small">@Html.DescriptionFor(m => m.XbmcFullUpdate)</span>
</label>
@Html.CheckBoxFor(m => m.XbmcFullUpdate, new { @class = "inputClass checkClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcCleanOnDownload)
<span class="small">@Html.DescriptionFor(m => m.XbmcCleanOnDownload)</span>
</label>
@Html.CheckBoxFor(m => m.XbmcCleanOnDownload, new { @class = "inputClass checkClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcCleanOnRename)
<span class="small">@Html.DescriptionFor(m => m.XbmcCleanOnRename)</span>
</label>
@Html.CheckBoxFor(m => m.XbmcCleanOnRename, new { @class = "inputClass checkClass" })
<label class="labelClass">@Html.LabelFor(m => m.XbmcHosts) <label class="labelClass">@Html.LabelFor(m => m.XbmcHosts)
<span class="small">@Html.DescriptionFor(m => m.XbmcHosts)</span> <span class="small">@Html.DescriptionFor(m => m.XbmcHosts)</span>

@ -39,10 +39,9 @@ Settings
} }
</div> </div>
</div> </div>
<button type="submit" id="save_button"> <br />
Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax" /> <button type="submit" id="save_button" >Save</button><img src="../../Content/Images/ajax-loader.gif" alt="Loader" id="saveAjax"/>
</div> </div>
} }
</div> </div>
<div id="result" class="hiddenResult"> <div id="result" class="hiddenResult">

Loading…
Cancel
Save