mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-21 05:53:33 -07:00
Xbmc Refactored
This commit is contained in:
parent
76fb548ccd
commit
b99e62c5ba
38 changed files with 1501 additions and 1521 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
190
NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs
Normal file
190
NzbDrone.Core/Notifications/Xbmc/HttpApiProvider.cs
Normal file
|
@ -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<ActivePlayer> GetActivePlayers(XbmcSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = new List<ActivePlayer>();
|
||||
var response = SendCommand(settings, "getcurrentlyplaying");
|
||||
|
||||
if (response.Contains("<li>Filename:[Nothing Playing]")) return new List<ActivePlayer>();
|
||||
if (response.Contains("<li>Type:Video")) result.Add(new ActivePlayer(1, "video"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.DebugException(ex.Message, ex);
|
||||
}
|
||||
|
||||
return new List<ActivePlayer>();
|
||||
}
|
||||
|
||||
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;<xml>;footer;</xml>;opentag;<tag>;closetag;</tag>;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);
|
||||
}
|
||||
}
|
||||
}
|
20
NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs
Normal file
20
NzbDrone.Core/Notifications/Xbmc/IApiProvider.cs
Normal file
|
@ -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<ActivePlayer> GetActivePlayers(XbmcSettings settings);
|
||||
bool CheckForError(string response);
|
||||
string GetSeriesPath(XbmcSettings settings, Series series);
|
||||
bool CanHandle(XbmcVersion version);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
225
NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs
Normal file
225
NzbDrone.Core/Notifications/Xbmc/JsonApiProvider.cs
Normal file
|
@ -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<ActivePlayer> 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<ActivePlayer>();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<ActivePlayersEdenResult>(response);
|
||||
|
||||
return result.Result;
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.DebugException(ex.Message, ex);
|
||||
}
|
||||
|
||||
return new List<ActivePlayer>();
|
||||
}
|
||||
|
||||
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<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;
|
||||
}
|
||||
|
||||
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<XbmcJsonResult<String>>(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<TvShow> 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<TvShow>();
|
||||
|
||||
var result = JsonConvert.DeserializeObject<TvShowResponse>(response);
|
||||
var shows = result.Result.TvShows;
|
||||
|
||||
return shows;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.DebugException(ex.Message, ex);
|
||||
}
|
||||
|
||||
return new List<TvShow>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
12
NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs
Normal file
12
NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs
Normal file
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ namespace NzbDrone.Core.Notifications.Xbmc
|
|||
{
|
||||
public class Xbmc : NotificationBase<XbmcSettings>
|
||||
{
|
||||
private readonly XbmcProvider _xbmcProvider;
|
||||
private readonly IXbmcService _xbmcProvider;
|
||||
|
||||
public Xbmc(XbmcProvider xbmcProvider, Logger logger)
|
||||
public Xbmc(IXbmcService xbmcProvider, Logger logger)
|
||||
{
|
||||
_xbmcProvider = xbmcProvider;
|
||||
}
|
||||
|
|
|
@ -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<XbmcJsonResult<String>>(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;<xml>;footer;</xml>;opentag;<tag>;closetag;</tag>;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<XbmcJsonResult<JObject>>(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<XbmcVersion>(versionObject.Value.ToString());
|
||||
|
||||
throw new InvalidCastException("Unknown Version structure!: " + versionObject);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.DebugException(ex.Message, ex);
|
||||
}
|
||||
|
||||
return new XbmcVersion();
|
||||
}
|
||||
|
||||
public virtual Dictionary<string, bool> 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<ActivePlayersDharmaResult>(response);
|
||||
|
||||
return result.Result;
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.DebugException(ex.Message, ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual List<ActivePlayer> 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<ActivePlayersEdenResult>(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 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<TvShowResponse>(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<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;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
118
NzbDrone.Core/Notifications/Xbmc/XbmcService.cs
Normal file
118
NzbDrone.Core/Notifications/Xbmc/XbmcService.cs
Normal file
|
@ -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<TestXbmcCommand>
|
||||
{
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
private readonly IHttpProvider _httpProvider;
|
||||
private readonly IEnumerable<IApiProvider> _apiProviders;
|
||||
|
||||
public XbmcService(IHttpProvider httpProvider, IEnumerable<IApiProvider> 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<XbmcJsonResult<JObject>>(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<XbmcVersion>(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!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue