using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Script.Serialization; using System.Xml.Linq; using Ninject; using NLog; using NzbDrone.Core.Model.Xbmc; using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Xbmc; using NzbDrone.Core.Repository; namespace NzbDrone.Core.Providers { public class XbmcProvider { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly ConfigProvider _configProvider; private readonly HttpProvider _httpProvider; private readonly EventClientProvider _eventClientProvider; [Inject] public XbmcProvider(ConfigProvider configProvider, HttpProvider httpProvider, EventClientProvider eventClientProvider) { _configProvider = configProvider; _httpProvider = httpProvider; _eventClientProvider = eventClientProvider; } public virtual void Notify(string header, string message) { //Always use EventServer, until Json has real support for it foreach (var host in _configProvider.XbmcHosts.Split(',')) { 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; foreach (var host in _configProvider.XbmcHosts.Split(',')) { Logger.Trace("Determining version of XBMC Host: {0}", host); var version = GetJsonVersion(host, username, password); Logger.Trace("Determining if there are any active players on XBMC host: {0}", host); var activePlayers = GetActivePlayers(host, username, password); //If video is currently playing, then skip update if (activePlayers["video"]) { Logger.Debug("Video is currently playing, skipping library update"); continue; } Logger.Trace("No video playing, proceeding with library update"); //If Dharma 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); } } public virtual bool UpdateWithJson(Series series, string host, string username, string password) { try { //Use Json! 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 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.SeriesId, 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() { //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); var command = "ExecBuiltIn(CleanLibrary(video))"; _eventClientProvider.SendAction(GetHostWithoutPort(host), ActionType.ExecBuiltin, command); } } public virtual string SendCommand(string host, string command, string username, string password) { var url = String.Format("http://{0}/xbmcCmds/xbmcHttp?command={1}", host, command); if (!String.IsNullOrEmpty(username)) { return _httpProvider.DownloadString(url, username, password); } return _httpProvider.DownloadString(url); } public virtual string GetXbmcSeriesPath(string host, int seriesId, string username, string password) { var query = String.Format( "select path.strPath from path, tvshow, tvshowlinkpath where tvshow.c12 = {0} and tvshowlinkpath.idShow = tvshow.idShow and tvshowlinkpath.idPath = path.idPath", seriesId); var command = String.Format("QueryVideoDatabase({0})", query); const string setResponseCommand = "SetResponseFormat(webheader;false;webfooter;false;header;;footer;;opentag;;closetag;;closefinaltag;false)"; const string resetResponseCommand = "SetResponseFormat()"; SendCommand(host, setResponseCommand, username, password); var response = SendCommand(host, command, username, password); SendCommand(host, resetResponseCommand, username, password); if (String.IsNullOrEmpty(response)) return String.Empty; var xDoc = XDocument.Load(new StringReader(response)); 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 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; Logger.Trace("Getting version from response"); var result = serializer.Deserialize(response); result.Result.TryGetValue("version", out version); } catch (Exception ex) { Logger.DebugException(ex.Message, ex); } return version; } public virtual Dictionary 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(response); return result.Result; } catch (Exception ex) { Logger.DebugException(ex.Message, ex); } return null; } public virtual List GetTvShowsJson(string host, string username, string password) { try { var 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(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 serializer = new JavaScriptSerializer(); var error = serializer.Deserialize(response); var code = error.Error["code"]; var message = error.Error["message"]; Logger.Debug("XBMC Json Error. Code = {0}, Message: {1}", code, message); return true; } return false; } private string GetHostWithoutPort(string address) { return address.Split(':')[0]; } } }