2011-03-06 21:45:35 +01:00
using System ;
2011-07-09 20:19:33 +02:00
using System.Collections.Generic ;
2011-03-06 22:36:09 +01:00
using System.IO ;
2011-03-06 21:45:35 +01:00
using System.Linq ;
2011-07-09 20:19:33 +02:00
using System.Web.Script.Serialization ;
2011-03-06 22:36:09 +01:00
using System.Xml.Linq ;
2012-01-05 01:41:42 +01:00
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
2011-03-06 21:45:35 +01:00
using NLog ;
2012-02-11 01:48:20 +01:00
using NzbDrone.Common ;
2013-02-24 07:48:52 +01:00
using NzbDrone.Core.Configuration ;
2013-02-19 07:01:03 +01:00
using NzbDrone.Core.Tv ;
2011-07-09 20:19:33 +02:00
using NzbDrone.Core.Model.Xbmc ;
using NzbDrone.Core.Providers.Xbmc ;
2011-03-06 21:45:35 +01:00
namespace NzbDrone.Core.Providers
{
2011-04-08 17:15:51 +02:00
public class XbmcProvider
2011-03-06 21:45:35 +01:00
{
2011-04-10 04:44:01 +02:00
private static readonly Logger Logger = LogManager . GetCurrentClassLogger ( ) ;
2013-02-24 07:48:52 +01:00
private readonly IConfigService _configService ;
2011-04-07 04:25:52 +02:00
private readonly HttpProvider _httpProvider ;
2011-07-09 20:19:33 +02:00
private readonly EventClientProvider _eventClientProvider ;
2011-03-06 21:45:35 +01:00
2013-02-24 07:48:52 +01:00
public XbmcProvider ( IConfigService configService , HttpProvider httpProvider , EventClientProvider eventClientProvider )
2011-03-06 21:45:35 +01:00
{
2013-02-24 07:48:52 +01:00
_configService = configService ;
2011-03-07 07:33:59 +01:00
_httpProvider = httpProvider ;
2011-07-09 20:19:33 +02:00
_eventClientProvider = eventClientProvider ;
2011-03-06 21:45:35 +01:00
}
2012-10-22 05:44:16 +02:00
public XbmcProvider ( )
{
}
2011-04-08 17:15:51 +02:00
public virtual void Notify ( string header , string message )
2011-03-06 21:45:35 +01:00
{
2011-07-09 20:19:33 +02:00
//Always use EventServer, until Json has real support for it
2013-02-24 07:48:52 +01:00
foreach ( var host in _configService . XbmcHosts . Split ( ',' ) )
2011-07-09 20:19:33 +02:00
{
Logger . Trace ( "Sending Notifcation to XBMC Host: {0}" , host ) ;
_eventClientProvider . SendNotification ( header , message , IconType . Jpeg , "NzbDrone.jpg" , GetHostWithoutPort ( host ) ) ;
}
}
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)
2013-02-24 07:48:52 +01:00
var username = _configService . XbmcUsername ;
var password = _configService . XbmcPassword ;
2011-03-06 21:45:35 +01:00
2013-02-24 07:48:52 +01:00
foreach ( var host in _configService . XbmcHosts . Split ( ',' ) )
2011-03-06 21:45:35 +01:00
{
2011-07-09 20:19:33 +02:00
Logger . Trace ( "Determining version of XBMC Host: {0}" , host ) ;
var version = GetJsonVersion ( host , username , password ) ;
2011-03-06 21:45:35 +01:00
2011-07-09 20:19:33 +02:00
//If Dharma
2012-12-13 22:52:54 +01:00
if ( version = = new XbmcVersion ( 2 ) )
2011-12-31 21:57:02 +01:00
{
2012-01-04 20:48:48 +01:00
//Check for active player only when we should skip updates when playing
2013-02-24 07:48:52 +01:00
if ( ! _configService . XbmcUpdateWhenPlaying )
2011-12-31 21:57:02 +01:00
{
2012-01-04 20:48:48 +01:00
Logger . Trace ( "Determining if there are any active players on XBMC host: {0}" , host ) ;
var activePlayers = GetActivePlayersDharma ( host , username , password ) ;
//If video is currently playing, then skip update
if ( activePlayers [ "video" ] )
{
Logger . Debug ( "Video is currently playing, skipping library update" ) ;
continue ;
}
2011-12-31 21:57:02 +01:00
}
2011-07-09 20:19:33 +02:00
UpdateWithHttp ( series , host , username , password ) ;
2011-12-31 21:57:02 +01:00
}
2011-07-09 20:19:33 +02:00
//If Eden or newer (attempting to make it future compatible)
2012-12-13 22:52:54 +01:00
else if ( version = = new XbmcVersion ( 3 ) | | version = = new XbmcVersion ( 4 ) )
2011-12-31 21:57:02 +01:00
{
2012-01-04 20:48:48 +01:00
//Check for active player only when we should skip updates when playing
2013-02-24 07:48:52 +01:00
if ( ! _configService . XbmcUpdateWhenPlaying )
2011-12-31 21:57:02 +01:00
{
2012-01-04 20:48:48 +01:00
Logger . Trace ( "Determining if there are any active players on XBMC host: {0}" , host ) ;
var activePlayers = GetActivePlayersEden ( host , 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" ) ;
continue ;
}
2011-12-31 21:57:02 +01:00
}
2012-12-14 06:18:15 +01:00
UpdateWithJsonExecBuiltIn ( series , host , username , password ) ;
2011-12-31 21:57:02 +01:00
}
2012-01-05 01:41:42 +01:00
2012-12-13 22:52:54 +01:00
else if ( version > = new XbmcVersion ( 5 ) )
{
//Check for active player only when we should skip updates when playing
2013-02-24 07:48:52 +01:00
if ( ! _configService . XbmcUpdateWhenPlaying )
2012-12-13 22:52:54 +01:00
{
Logger . Trace ( "Determining if there are any active players on XBMC host: {0}" , host ) ;
var activePlayers = GetActivePlayersEden ( host , 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" ) ;
continue ;
}
}
2012-12-14 06:18:15 +01:00
UpdateWithJsonVideoLibraryScan ( series , host , username , password ) ;
2012-12-13 22:52:54 +01:00
}
2012-01-05 01:41:42 +01:00
//Log Version zero if check failed
else
Logger . Trace ( "Unknown version: [{0}], skipping." , version ) ;
2011-03-06 21:45:35 +01:00
}
2011-07-09 20:19:33 +02:00
}
2011-03-06 21:45:35 +01:00
2012-12-14 06:18:15 +01:00
public virtual bool UpdateWithJsonExecBuiltIn ( Series series , string host , string username , string password )
2011-07-09 20:19:33 +02:00
{
try
2011-03-06 22:36:09 +01:00
{
2011-07-09 20:19:33 +02:00
//Use Json!
var xbmcShows = GetTvShowsJson ( host , username , password ) ;
2012-01-06 07:29:02 +01:00
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
2013-02-26 04:58:57 +01:00
path = xbmcShows . FirstOrDefault ( s = > s . ImdbNumber = = series . Id | | s . Label = = series . Title ) ;
2011-07-09 20:19:33 +02:00
2012-01-10 08:10:53 +01:00
//var hostOnly = GetHostWithoutPort(host);
2011-07-09 20:19:33 +02:00
if ( path ! = null )
{
2012-01-06 07:29:02 +01:00
Logger . Trace ( "Updating series [{0}] (Path: {1}) on XBMC host: {2}" , series . Title , path . File , host ) ;
2012-01-10 08:10:53 +01:00
var command = String . Format ( "ExecBuiltIn(UpdateLibrary(video,{0}))" , path . File ) ;
SendCommand ( host , command , username , password ) ;
2011-07-09 20:19:33 +02:00
}
else
{
Logger . Trace ( "Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library" , series . Title , host ) ;
2012-01-10 08:10:53 +01:00
SendCommand ( host , "ExecBuiltIn(UpdateLibrary(video))" , username , password ) ;
2011-07-09 20:19:33 +02:00
}
2011-03-06 22:36:09 +01:00
}
2011-07-09 20:19:33 +02:00
catch ( Exception ex )
{
Logger . DebugException ( ex . Message , ex ) ;
return false ;
}
return true ;
2011-03-06 21:45:35 +01:00
}
2012-12-14 06:18:15 +01:00
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
2013-02-26 04:58:57 +01:00
path = xbmcShows . FirstOrDefault ( s = > s . ImdbNumber = = series . Id | | s . Label = = series . Title ) ;
2012-12-14 06:18:15 +01:00
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 ;
}
2011-07-09 20:19:33 +02:00
public virtual bool UpdateWithHttp ( Series series , string host , string username , string password )
2011-03-06 21:45:35 +01:00
{
2011-07-09 20:19:33 +02:00
try
2011-03-06 22:36:09 +01:00
{
Logger . Trace ( "Sending Update DB Request to XBMC Host: {0}" , host ) ;
2013-02-26 04:58:57 +01:00
var xbmcSeriesPath = GetXbmcSeriesPath ( host , series . Id , username , password ) ;
2011-03-06 22:36:09 +01:00
2011-07-09 20:19:33 +02:00
//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
2011-03-06 22:36:09 +01:00
{
//Update the entire library
2011-07-09 20:19:33 +02:00
Logger . Trace ( "Series [{0}] doesn't exist on XBMC host: {1}, Updating Entire Library" , series . Title , host ) ;
SendCommand ( host , "ExecBuiltIn(UpdateLibrary(video))" , username , password ) ;
2011-03-06 22:36:09 +01:00
}
2011-07-09 20:19:33 +02:00
}
2011-03-06 22:36:09 +01:00
2011-07-09 20:19:33 +02:00
catch ( Exception ex )
{
Logger . DebugException ( ex . Message , ex ) ;
return false ;
2011-03-06 22:36:09 +01:00
}
2011-07-09 20:19:33 +02:00
return true ;
2011-03-06 21:45:35 +01:00
}
2011-04-08 17:15:51 +02:00
public virtual void Clean ( )
2011-03-06 21:45:35 +01:00
{
2011-07-09 20:19:33 +02:00
//Use EventServer, once Dharma is extinct use Json?
2013-02-24 07:48:52 +01:00
foreach ( var host in _configService . XbmcHosts . Split ( ',' ) )
2011-03-06 22:36:09 +01:00
{
Logger . Trace ( "Sending DB Clean Request to XBMC Host: {0}" , host ) ;
2011-07-09 20:19:33 +02:00
var command = "ExecBuiltIn(CleanLibrary(video))" ;
_eventClientProvider . SendAction ( GetHostWithoutPort ( host ) , ActionType . ExecBuiltin , command ) ;
2011-03-06 22:36:09 +01:00
}
2011-03-06 21:45:35 +01:00
}
2011-07-09 20:19:33 +02:00
public virtual string SendCommand ( string host , string command , string username , string password )
2011-03-06 21:45:35 +01:00
{
var url = String . Format ( "http://{0}/xbmcCmds/xbmcHttp?command={1}" , host , command ) ;
2011-03-07 07:33:59 +01:00
if ( ! String . IsNullOrEmpty ( username ) )
2011-03-06 21:45:35 +01:00
{
2011-03-07 07:33:59 +01:00
return _httpProvider . DownloadString ( url , username , password ) ;
2011-03-06 21:45:35 +01:00
}
2011-04-10 04:44:01 +02:00
2011-03-07 07:33:59 +01:00
return _httpProvider . DownloadString ( url ) ;
2011-03-06 21:45:35 +01:00
}
2011-03-06 22:36:09 +01:00
2011-07-09 20:19:33 +02:00
public virtual string GetXbmcSeriesPath ( string host , int seriesId , string username , string password )
2011-03-06 22:36:09 +01:00
{
2011-04-10 04:44:01 +02:00
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 ) ;
2011-03-06 22:36:09 +01:00
var command = String . Format ( "QueryVideoDatabase({0})" , query ) ;
2011-07-09 20:19:33 +02:00
const string setResponseCommand =
2011-04-10 04:44:01 +02:00
"SetResponseFormat(webheader;false;webfooter;false;header;<xml>;footer;</xml>;opentag;<tag>;closetag;</tag>;closefinaltag;false)" ;
2011-07-09 20:19:33 +02:00
const string resetResponseCommand = "SetResponseFormat()" ;
2011-03-06 22:36:09 +01:00
2011-07-09 20:19:33 +02:00
SendCommand ( host , setResponseCommand , username , password ) ;
var response = SendCommand ( host , command , username , password ) ;
SendCommand ( host , resetResponseCommand , username , password ) ;
2011-03-06 22:36:09 +01:00
if ( String . IsNullOrEmpty ( response ) )
return String . Empty ;
2011-09-29 06:39:05 +02:00
var xDoc = XDocument . Load ( new StringReader ( response . Replace ( "&" , "&" ) ) ) ;
2011-03-06 22:36:09 +01:00
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 ;
}
2011-07-09 20:19:33 +02:00
2012-12-13 22:52:54 +01:00
public virtual XbmcVersion GetJsonVersion ( string host , string username , string password )
2011-07-09 20:19:33 +02:00
{
//2 = Dharma
2012-12-13 22:52:54 +01:00
//3 & 4 = Eden
//5 & 6 = Frodo
2011-07-09 20:19:33 +02:00
try
{
2012-01-05 01:41:42 +01:00
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 ( ) ) ;
2011-07-09 20:19:33 +02:00
if ( CheckForJsonError ( response ) )
2012-12-13 22:52:54 +01:00
return new XbmcVersion ( ) ;
2011-07-09 20:19:33 +02:00
2011-09-27 02:17:41 +02:00
Logger . Trace ( "Getting version from response" ) ;
2012-12-13 22:52:54 +01:00
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 ) ;
2011-07-09 20:19:33 +02:00
}
catch ( Exception ex )
{
Logger . DebugException ( ex . Message , ex ) ;
}
2012-12-13 22:52:54 +01:00
return new XbmcVersion ( ) ;
2011-07-09 20:19:33 +02:00
}
2011-12-31 21:57:02 +01:00
public virtual Dictionary < string , bool > GetActivePlayersDharma ( string host , string username , string password )
2011-07-09 20:19:33 +02:00
{
2011-12-31 21:57:02 +01:00
try
{
2012-01-05 01:41:42 +01:00
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 ( ) ) ;
2011-12-31 21:57:02 +01:00
if ( CheckForJsonError ( response ) )
return null ;
2012-01-05 01:41:42 +01:00
var result = JsonConvert . DeserializeObject < ActivePlayersDharmaResult > ( response ) ;
2011-12-31 21:57:02 +01:00
return result . Result ;
}
2011-07-09 20:19:33 +02:00
2011-12-31 21:57:02 +01:00
catch ( Exception ex )
{
Logger . DebugException ( ex . Message , ex ) ;
}
return null ;
}
public virtual List < ActivePlayer > GetActivePlayersEden ( string host , string username , string password )
{
2011-07-09 20:19:33 +02:00
try
{
2012-01-05 01:41:42 +01:00
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 ( ) ) ;
2011-07-09 20:19:33 +02:00
if ( CheckForJsonError ( response ) )
return null ;
2012-01-05 01:41:42 +01:00
var result = JsonConvert . DeserializeObject < ActivePlayersEdenResult > ( response ) ;
2011-07-09 20:19:33 +02:00
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
{
2012-01-05 01:41:42 +01:00
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 ( ) ) ;
2011-07-09 20:19:33 +02:00
if ( CheckForJsonError ( response ) )
return null ;
2012-01-05 01:41:42 +01:00
var result = JsonConvert . DeserializeObject < TvShowResponse > ( response ) ;
var shows = result . Result . TvShows ;
2011-07-09 20:19:33 +02:00
return shows ;
}
catch ( Exception ex )
{
Logger . DebugException ( ex . Message , ex ) ;
}
return null ;
}
public virtual bool CheckForJsonError ( string response )
{
2011-09-27 02:17:41 +02:00
Logger . Trace ( "Looking for error in response: {0}" , response ) ;
2011-07-09 20:19:33 +02:00
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 ;
}
2011-09-27 19:41:36 +02:00
if ( String . IsNullOrWhiteSpace ( response ) )
{
Logger . Debug ( "Invalid response from XBMC, the response is not valid JSON" ) ;
return true ;
}
2011-07-09 20:19:33 +02:00
return false ;
}
2012-10-22 05:44:16 +02:00
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" , GetHostWithoutPort ( 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 ) ;
2012-12-13 22:52:54 +01:00
if ( version = = new XbmcVersion ( ) )
2012-10-22 05:44:16 +02:00
throw new Exception ( "Failed to get JSON version in test" ) ;
}
}
2011-07-09 20:19:33 +02:00
private string GetHostWithoutPort ( string address )
{
return address . Split ( ':' ) [ 0 ] ;
}
2011-03-06 21:45:35 +01:00
}
2011-04-10 04:44:01 +02:00
}