1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-05 10:49:37 +02:00

Add XBMC Localplay

This commit is contained in:
Afterster 2013-11-02 15:17:34 +01:00 committed by Paul Arthur
parent 872012a24f
commit 539ca08efc
18 changed files with 1703 additions and 0 deletions

View file

@ -0,0 +1,327 @@
<?php
require_once 'ClientException.php';
require_once 'ConnectionException.php';
require_once 'RequestException.php';
require_once 'ResponseException.php';
require_once 'Server.php';
require_once 'Namespace.php';
require_once 'Response.php';
abstract class XBMC_RPC_Client {
/**
* @var XBMC_RPC_Server A server object instance representing the server
* to be used for remote procedure calls.
* @access protected
*/
protected $server;
/**
* @var XBMC_RPC_Namespace The root namespace instance.
* @access private
*/
private $rootNamespace;
/**
* @var bool A flag to indicate if the JSON-RPC version is legacy, ie before
* the XBMC Eden updates. This can be used to determine the format of commands
* to be used with this library, allowing client code to support legacy systems.
* @access private
*/
private $isLegacy = false;
/**
* Constructor.
*
* Connects to the server and populates a list of available commands by
* having the server introspect.
*
* @param mixed $parameters An associative array of connection parameters,
* or a valid connection URI as a string. If supplying an array, the following
* paramters are accepted: host, port, user and pass. Any other parameters
* are discarded.
* @exception XBMC_RPC_ConnectionException if it is not possible to connect to
* the server.
* @access public
*/
public function __construct($parameters) {
try {
$server = new XBMC_RPC_Server($parameters);
} catch (XBMC_RPC_ServerException $e) {
throw new XBMC_RPC_ConnectionException($e->getMessage(), $e->getCode(), $e);
}
$this->server = $server;
$this->prepareConnection();
$this->assertCanConnect();
$this->createRootNamespace();
}
/**
* Delegates any direct Command calls to the root namespace.
*
* @param string $name The name of the called command.
* @param mixed $arguments An array of arguments used to call the command.
* @return The result of the command call as returned from the namespace.
* @exception XBMC_RPC_InvalidCommandException if the called command does not
* exist in the root namespace.
* @access public
*/
public function __call($name, array $arguments) {
return call_user_func_array(array($this->rootNamespace, $name), $arguments);
}
/**
* Delegates namespace accesses to the root namespace.
*
* @param string $name The name of the requested namespace.
* @return XBMC_RPC_Namespace The requested namespace.
* @exception XBMC_RPC_InvalidNamespaceException if the namespace does not
* exist in the root namespace.
* @access public
*/
public function __get($name) {
return $this->rootNamespace->$name;
}
/**
* Executes a remote procedure call using the supplied XBMC_RPC_Command
* object.
*
* @param XBMC_RPC_Command The command to execute.
* @return XBMC_RPC_Response The response from the remote procedure call.
* @access public
*/
public function executeCommand(XBMC_RPC_Command $command) {
return $this->sendRpc($command->getFullName(), $command->getArguments());
}
/**
* Determines if the XBMC system to which the client is connected is legacy
* (pre Eden) or not. This is useful because the format of commands/params
* is different in the Eden RPC implementation.
*
* @return bool True if the system is legacy, false if not.
* @access public
*/
public function isLegacy() {
return $this->isLegacy;
}
/**
* Asserts that the server is reachable and a connection can be made.
*
* @return void
* @exception XBMC_RPC_ConnectionException if it is not possible to connect to
* the server.
* @abstract
* @access protected
*/
protected abstract function assertCanConnect();
/**
* Prepares for a connection to XBMC.
*
* Should be used by child classes for any pre-connection logic which is necessary.
*
* @return void
* @exception XBMC_RPC_ClientException if it was not possible to prepare for
* connection successfully.
* @abstract
* @access protected
*/
protected abstract function prepareConnection();
/**
* Sends a JSON-RPC request to XBMC and returns the result.
*
* @param string $json A JSON-encoded string representing the remote procedure call.
* This string should conform to the JSON-RPC 2.0 specification.
* @param string $rpcId The unique ID of the remote procedure call.
* @return string The JSON-encoded response string from the server.
* @exception XBMC_RPC_RequestException if it was not possible to make the request.
* @access protected
* @link http://groups.google.com/group/json-rpc/web/json-rpc-2-0 JSON-RPC 2.0 specification
*/
protected abstract function sendRequest($json, $rpcId);
/**
* Build a JSON-RPC 2.0 compatable json_encoded string representing the
* specified command, parameters and request id.
*
* @param string $command The name of the command to be called.
* @param mixed $params An array of paramters to be passed to the command.
* @param string $rpcId A unique string used for identifying the request.
* @access private
*/
private function buildJson($command, $params, $rpcId) {
$data = array(
'jsonrpc' => '2.0',
'method' => $command,
'params' => $params,
'id' => $rpcId
);
return json_encode($data);
}
/**
* Ensures that the recieved response from a remote procedure call is valid.
*
* $param XBMC_RPC_Response $response A response object encapsulating remote
* procedure call response data as returned from Client::sendRequest().
* @return bool True of the reponse is valid, false if not.
* @access private
*/
private function checkResponse(XBMC_RPC_Response $response, $rpcId) {
return ($response->getId() == $rpcId);
}
/**
* Creates the root namespace instance.
*
* @return void
* @access private
*/
private function createRootNamespace() {
$commands = $this->loadAvailableCommands();
$this->rootNamespace = new XBMC_RPC_Namespace('root', $commands, $this);
}
/**
* Generates a unique string to be used as a remote procedure call ID.
*
* @return string A unique string.
* @access private
*/
private function getRpcId() {
return uniqid();
}
/**
* Retrieves an array of commands by requesting the RPC server to introspect.
*
* @return mixed An array of available commands which may be executed on the server.
* @exception XBMC_RPC_RequestException if it is not possible to retrieve a list of
* available commands.
* @access private
*/
private function loadAvailableCommands() {
try {
$response = $this->sendRpc('JSONRPC.Introspect');
} catch (XBMC_RPC_Exception $e) {
throw new XBMC_RPC_RequestException(
'Unable to retrieve list of available commands: ' . $e->getMessage()
);
}
if (isset($response['commands'])) {
$this->isLegacy = true;
return $this->loadAvailableCommandsLegacy($response);
}
$commands = array();
foreach (array_keys($response['methods']) as $command) {
$array = $this->commandStringToArray($command);
$commands = $this->mergeCommandArrays($commands, $array);
}
return $commands;
}
/**
* Retrieves an array of commands by requesting the RPC server to introspect.
*
* This method supports the legacy implementation of XBMC's RPC.
*
* @return mixed An array of available commands which may be executed on the server.
* @access private
*/
private function loadAvailableCommandsLegacy($response) {
$commands = array();
foreach ($response['commands'] as $command) {
$array = $this->commandStringToArray($command['command']);
$commands = $this->mergeCommandArrays($commands, $array);
}
return $commands;
}
/**
* Converts a dot-delimited command name to a multidimensional array format.
*
* @return mixed An array representing the command.
* @access private
*/
private function commandStringToArray($command) {
$path = explode('.', $command);
if (count($path) === 1) {
$commands[] = $path[0];
continue;
}
$command = array_pop($path);
$array = array();
$reference =& $array;
foreach ($path as $i => $key) {
if (is_numeric($key) && intval($key) > 0 || $key === '0') {
$key = intval($key);
}
if ($i === count($path) - 1) {
$reference[$key] = array($command);
} else {
if (!isset($reference[$key])) {
$reference[$key] = array();
}
$reference =& $reference[$key];
}
}
return $array;
}
/**
* Recursively merges the supplied arrays whilst ensuring that commands are
* not duplicated within a namespace.
*
* Note that array_merge_recursive is not suitable here as it does not ensure
* that values are distinct within an array.
*
* @param mixed $base The base array into which $append will be merged.
* @param mixed $append The array to merge into $base.
* @return mixed The merged array of commands and namespaces.
* @access private
*/
private function mergeCommandArrays(array $base, array $append) {
foreach ($append as $key => $value) {
if (!array_key_exists($key, $base) && !is_numeric($key)) {
$base[$key] = $append[$key];
continue;
}
if (is_array($value) || is_array($base[$key])) {
$base[$key] = $this->mergeCommandArrays($base[$key], $append[$key]);
} elseif (is_numeric($key)) {
if (!in_array($value, $base)) {
$base[] = $value;
}
} else {
$base[$key] = $value;
}
}
return $base;
}
/**
* Executes a remote procedure call using the supplied command name and parameters.
*
* @param string $command The full, dot-delimited name of the command to call.
* @param mixed $params An array of parameters to be passed to the called method.
* @return mixed The data returned from the response.
* @exception XBMC_RPC_RequestException if it was not possible to make the request.
* @exception XBMC_RPC_ResponseException if the response was not being properly received.
* @access private
*/
private function sendRpc($command, $params = array()) {
$rpcId = $this->getRpcId();
$json = $this->buildJson($command, $params, $rpcId);
$response = new XBMC_RPC_Response($this->sendRequest($json, $rpcId));
if (!$this->checkResponse($response, $rpcId)) {
throw new XBMC_RPC_ResponseException('JSON RPC request/response ID mismatch');
}
return $response->getData();
}
}

View file

@ -0,0 +1,7 @@
<?php
require_once 'Exception.php';
class XBMC_RPC_ClientException extends XBMC_RPC_Exception {
}

View file

@ -0,0 +1,86 @@
<?php
/**
* A JSON-RPC command.
*/
class XBMC_RPC_Command {
/**
* @var string The name of the command.
* @access private
*/
private $name;
/**
* @var The namespace to which the instance belongs.
* @access private
*/
private $parentNamespace;
/**
* @var The client to be used for executing the command.
* @access private
*/
private $client;
/**
* @var An array of arguments to be passed with the command.
* @access private
*/
private $arguments = array();
/**
* Constructor.
*
* @param string $name The name of the desired command.
* @param XBMC_RPC_Client $client The client instance responsible for operating
* the Command instance.
* @param mixed $parent The parent XBMC_RPC_Command object, or null if there is
* no parent of this instance.
* @access public
*/
public function __construct($name, XBMC_RPC_Client $client, XBMC_RPC_Namespace $parent) {
$this->name = $name;
$this->parentNamespace = $parent;
$this->client = $client;
}
/**
* Executes the remote procedure call command.
*
* @param mixed $arguments An array of arguments to be passed along with the
* command.
* @return mixed The response data as returned from XBMC_RPC_Client::sendRpc().
* @exception XBMC_RPC_Exception if the remote procedure call could be carried
* out successfully.
* @access public
*/
public function execute(array $arguments = array()) {
if (count($arguments) == 1) {
$arguments = array_shift($arguments);
}
$this->arguments = $arguments;
return $this->client->executeCommand($this);
}
/**
* Gets an array of arguments which accompany the command.
*
* @return mixed The array of argument which accompany this command.
* @access public
*/
public function getArguments() {
return $this->arguments;
}
/**
* Gets the full, dot-delimited name of the command including its namespace path.
*
* @return string The command name.
* @access public
*/
public function getFullName() {
return $this->parentNamespace->getFullName() . '.' . $this->name;
}
}

View file

@ -0,0 +1,7 @@
<?php
require_once 'Exception.php';
class XBMC_RPC_CommandException extends XBMC_RPC_Exception {
}

View file

@ -0,0 +1,7 @@
<?php
require_once 'Exception.php';
class XBMC_RPC_ConnectionException extends XBMC_RPC_Exception {
}

View file

@ -0,0 +1,5 @@
<?php
class XBMC_RPC_Exception extends Exception {
}

View file

@ -0,0 +1,136 @@
<?php
require_once 'Client.php';
class XBMC_RPC_HTTPClient extends XBMC_RPC_Client {
/**
* @var string The URI of the XBMC server to which requests will be made.
*/
private $uri;
/**
* @var resource The curl resource used for making requests.
*/
private $curlResource;
/**
* @var int The amount of time for which the script will try to connect before giving up.
*/
private $timeout = 15;
/**
* Destructor.
*
* Cleans up the curl resource if necessary.
*/
public function __destruct() {
if (is_resource($this->curlResource)) {
curl_close($this->curlResource);
}
}
/**
* Sets the number of seconds the script will wait while trying to connect
* to the server before giving up.
*
* @param int $seconds The number of seconds to wait.
* @return void
*/
public function setTimeout($seconds) {
$this->timeout = (int) $seconds;
if ($this->timeout < 0) {
$this->timeout = 0;
}
}
/**
* Asserts that the server is reachable and a connection can be made.
*
* @return void
* @exception XBMC_RPC_ConnectionException if it is not possible to connect to
* the server.
*/
protected function assertCanConnect() {
if (extension_loaded('curl')) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $this->uri);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
if (!curl_exec($ch) || !in_array(curl_getinfo($ch, CURLINFO_HTTP_CODE), array('200', '401'))) {
throw new XBMC_RPC_ConnectionException('Unable to connect to XBMC server via HTTP');
}
} else {
throw new XBMC_RPC_ConnectionException('Cannot test if XBMC server is reachable via HTTP because cURL is not installed');
}
}
/**
* Prepares for a connection to XBMC via HTTP.
*
* @exception XBMC_RPC_ClientException if it was not possible to prepare for
* connection successfully.
* @access protected
*/
protected function prepareConnection() {
if (!$uri = $this->buildUri()) {
throw new XBMC_RPC_ClientException('Unable to parse server parameters into valid URI string');
}
$this->uri = $uri;
}
/**
* Sends a JSON-RPC request to XBMC and returns the result.
*
* @param string $json A JSON-encoded string representing the remote procedure call.
* This string should conform to the JSON-RPC 2.0 specification.
* @param string $rpcId The unique ID of the remote procedure call.
* @return string The JSON-encoded response string from the server.
* @exception XBMC_RPC_RequestException if it was not possible to make the request.
* @access protected
* @link http://groups.google.com/group/json-rpc/web/json-rpc-2-0 JSON-RPC 2.0 specification
*/
protected function sendRequest($json, $rpcId) {
if (empty($this->curlResource)) {
$this->curlResource = $this->createCurlResource();
}
curl_setopt($this->curlResource, CURLOPT_POSTFIELDS, $json);
curl_setopt($this->curlResource, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
if (!$response = curl_exec($this->curlResource)) {
throw new XBMC_RPC_RequestException('Could not make a request the server');
}
return $response;
}
/**
* Builds the server URI from the supplied parameters.
*
* @return string The server URI.
* @access private
*/
private function buildUri() {
$parameters = $this->server->getParameters();
$credentials = '';
if (!empty($parameters['user'])) {
$credentials = $parameters['user'];
$credentials .= empty($parameters['pass']) ? '@' : ':' . $parameters['pass'] . '@';
}
return sprintf('http://%s%s:%d/jsonrpc', $credentials, $parameters['host'], $parameters['port']);
}
/**
* Creates a curl resource with the correct settings for making JSON-RPC calls
* to XBMC.
*
* @return resource A new curl resource.
* @access private
*/
private function createCurlResource() {
$curlResource = curl_init();
curl_setopt($curlResource, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curlResource, CURLOPT_POST, 1);
curl_setopt($curlResource, CURLOPT_URL, $this->uri);
return $curlResource;
}
}

View file

@ -0,0 +1,7 @@
<?php
require_once 'Exception.php';
class XBMC_RPC_InvalidCommandException extends XBMC_RPC_Exception {
}

View file

@ -0,0 +1,7 @@
<?php
require_once 'Exception.php';
class XBMC_RPC_InvalidNamespaceException extends XBMC_RPC_Exception {
}

View file

@ -0,0 +1,166 @@
<?php
require_once 'InvalidNamespaceException.php';
require_once 'InvalidCommandException.php';
require_once 'Command.php';
/**
* A collection of commands and namespaces.
*/
class XBMC_RPC_Namespace {
/**
* @var string The name of the namespace.
* @access private
*/
private $name;
/**
* @var XBMC_RPC_Command The parent namespace of the current instance.
* @access private
*/
private $parentNamespace;
/**
* @var The tree of commands and namespaces which are children of the current
* instance.
* @access private
*/
private $children = array();
/**
* @var A cache of child XBMC_RPC_Command and XBMC_RPC_Namespace objects.
* @access private
*/
private $objectCache = array();
/**
* @var An instance of XBMC_RPC_Client to which this XBMC_RPC_Namespace
* instance belongs.
* @access private
*/
private $client;
/**
* Constructor.
*
* @param string $name The name of the namespace.
* @param mixed $children An array of commands and namespaces which are
* children of the current instance.
* @param XBMC_RPC_Client $client The client instance to which this instance
* belongs.
* @param mixed $parent The parent XBMC_RPC_Namespace object, or null if this
* instance is the root namespace.
* @access public
*/
public function __construct($name, array $children, XBMC_RPC_Client $client, XBMC_RPC_Namespace $parent = null) {
$this->name = $name;
$this->children = $children;
$this->client = $client;
$this->parentNamespace = $parent;
}
/**
* Executes the called command.
*
* @param string $name The name of the command to call.
* @arguments mixed An array of arguments to be send with the remote
* procedure call.
* @return XBMC_RPC_Response The response of the remote procedure call.
* @exception XBMC_RPC_InvalidCommandException if the requested command does
* not exist in this namespace.
* @access public
*/
public function __call($name, array $arguments) {
$this->assertHasChildCommand($name);
if (empty($this->objectCache[$name])) {
$this->objectCache[$name] = new XBMC_RPC_Command($name, $this->client, $this);
}
return $this->objectCache[$name]->execute($arguments);
}
/**
* Gets the requested child namespace.
*
* @param string $name The name of the namespace to get.
* @return XBMC_RPC_Namespace The requested child namespace.
* @exception XBMC_RPC_InvalidNamespaceException if the requested namespace does
* not exist in this namespace.
* @access public
*/
public function __get($name) {
$this->assertHasChildNamespace($name);
if (empty($this->objectCache[$name])) {
$this->objectCache[$name] = new XBMC_RPC_Namespace($name, $this->children[$name], $this->client, $this);
}
return $this->objectCache[$name];
}
/**
* Gets the full dot-delimited string representing the path from the root
* namespace to the current namespace.
*
* @return string The dot-delimited string.
* @access public
*/
public function getFullName() {
$name = '';
if (!empty($this->parentNamespace)) {
$name = $this->parentNamespace->getFullName() . '.' . $this->name;
}
return trim($name, '.');
}
/**
* Asserts that the the namespace contains the specified command as a direct
* child.
*
* @param string $name The name of the command to check for.
* @exception XBMC_RPC_InvalidCommandException if the command is not a direct
* child of this namespace.
* @access private
*/
private function assertHasChildCommand($name) {
if (!$this->hasChildCommand($name)) {
throw new XBMC_RPC_InvalidCommandException("Command $name does not exist in namespace $this->name");
}
}
/**
* Asserts that the the namespace contains the specified namespace as a direct
* child.
*
* @param string $name The name of the namespace to check for.
* @exception XBMC_RPC_InvalidNamespaceException if the namespace is not a direct
* child of this namespace.
* @access private
*/
private function assertHasChildNamespace($name) {
if (!$this->hasChildNamespace($name)) {
throw new XBMC_RPC_InvalidNamespaceException("Namespace $name does not exist in namespace $this->name");
}
}
/**
* Checks if the namespace has the specified command as a direct child.
*
* @param string $name The name of the command to check for.
* @return bool True if the command exists in this namespace, false if not.
* @access private
*/
private function hasChildCommand($name) {
return in_array($name, $this->children);
}
/**
* Checks if the namespace has the specified namespace as a direct child.
*
* @param string $name The name of the namespace to check for.
* @return bool True if the namespace exists in this namespace, false if not.
* @access private
*/
private function hasChildNamespace($name) {
return array_key_exists($name, $this->children);
}
}

View file

@ -0,0 +1,7 @@
<?php
require_once 'Exception.php';
class XBMC_RPC_RequestException extends XBMC_RPC_Exception {
}

View file

@ -0,0 +1,66 @@
<?php
class XBMC_RPC_Response {
/**
* @var string The id of the response as returned from the server.
* @access private
*/
private $id;
/**
* @var string The data returned from the server if the response was successful.
* @access private
*/
private $data;
/**
* Constructor.
*
* @param string $response The JSON-decoded response string
* as returned from the server
*/
public function __construct($response) {
$response = $this->decodeResponse($response);
$this->id = $response['id'];
if (isset($response['error'])) {
throw new XBMC_RPC_ResponseException($response['error']['message'], $response['error']['code']);
} elseif (!isset($response['result'])) {
throw new XBMC_RPC_ResponseException('Invalid JSON RPC response');
}
$this->data = $response['result'];
}
/**
* Gets the response data.
*
* @return mixed The response data.
*/
public function getData() {
return $this->data;
}
/**
* Gets the response id.
*
* @return string The response id.
*/
public function getId() {
return $this->id;
}
/**
* Takes a JSON string as returned from the server and decodes it into an
* associative array.
*/
private function decodeResponse($json) {
if (extension_loaded('mbstring')) {
$encoding = mb_detect_encoding($json, 'ASCII,UTF-8,ISO-8859-1,windows-1252,iso-8859-15');
if ($encoding && !in_array($encoding, array('UTF-8', 'ASCII'))) {
$json = mb_convert_encoding($json, 'UTF-8', $encoding);
}
}
return json_decode($json, true);
}
}

View file

@ -0,0 +1,7 @@
<?php
require_once 'Exception.php';
class XBMC_RPC_ResponseException extends XBMC_RPC_Exception {
}

View file

@ -0,0 +1,88 @@
<?php
require_once 'ServerException.php';
class XBMC_RPC_Server {
/**
* @var mixed An array of parameters used to connect to the server.
* @access private
*/
private $parameters = array();
/**
* Constructor.
*
* @param mixed $parameters An associative array of connection parameters,
* or a valid connection URI as a string. If supplying an array, the following
* paramters are accepted: host, port, user and pass. Any other parameters
* are discarded.
* @exception XBMC_RPC_ServerException if the supplied parameters could not
* be parsed successfully.
* @access public
*/
public function __construct($parameters) {
if (!$parameters = $this->parseParameters($parameters)) {
throw new XBMC_RPC_ServerException('Unable to parse server parameters');
}
$this->parameters = $parameters;
}
/**
* Checks if the server is connected.
*
* @return bool True if the server is connected, false if not.
* @access public
*/
public function isConnected() {
return $this->connected;
}
/**
* Gets the connection parameters/
*
* @return mixed The connection parameters as an associative array.
* @access public
*/
public function getParameters() {
return $this->parameters;
}
/**
* Parses the supplied parameters into a standard associative array format.
*
* @param mixed $parameters An associative array of connection parameters,
* or a valid connection URI as a string. If supplying an array, the following
* paramters are accepted: host, port, user and pass. Any other parameters
* are discarded.
* @return mixed The connection parameters as an associative array, or false
* if the parameters could not be parsed. The array will have the following
* keys: host, port, user and pass.
* @access private
*/
private function parseParameters($parameters) {
if (is_string($parameters)) {
$parameters = preg_replace('#^[a-z]+://#i', '', trim($parameters));
if (!$parameters = parse_url('http://' . $parameters)) {
return false;
}
}
if (!is_array($parameters)) {
// if parameters are not a string or an array, something is wrong
return false;
}
$defaults = array(
'host' => 'localhost',
'port' => 8080,
'user' => null,
'pass' => null
);
$parameters = array_intersect_key(array_merge($defaults, $parameters), $defaults);
return $parameters;
}
}

View file

@ -0,0 +1,7 @@
<?php
require_once 'Exception.php';
class XBMC_RPC_ServerException extends XBMC_RPC_Exception {
}

View file

@ -0,0 +1,139 @@
<?php
require_once 'Client.php';
class XBMC_RPC_TCPClient extends XBMC_RPC_Client {
/**
* @var resource A file pointer resource for reading over the connected socket.
* @access private
*/
private $fp;
/**
* Destructor.
*
* Cleans up the file resource if necessary.
*/
public function __destruct() {
if (is_resource($this->fp)) {
fclose($this->fp);
}
}
/**
* Asserts that the server is reachable and a connection can be made.
*
* @return void
* @exception XBMC_RPC_ConnectionException if it is not possible to connect to
* the server.
* @access protected
*/
protected function assertCanConnect() {
if (!$this->canConnect()) {
throw new XBMC_RPC_ConnectionException('Unable to connect to XBMC server via TCP');
}
}
/**
* Prepares for a connection to XBMC via TCP.
*
* @return void
* @access protected
*/
protected function prepareConnection() {
$parameters = $this->server->getParameters();
$this->fp = @fsockopen($parameters['host'], $parameters['port']);
}
/**
* Sends a JSON-RPC request to XBMC and returns the result.
*
* @param string $json A JSON-encoded string representing the remote procedure call.
* This string should conform to the JSON-RPC 2.0 specification.
* @param string $rpcId The unique ID of the remote procedure call.
* @return string The JSON-encoded response string from the server.
* @exception XBMC_RPC_RequestException if it was not possible to make the request.
* @access protected
* @link http://groups.google.com/group/json-rpc/web/json-rpc-2-0 JSON-RPC 2.0 specification
*/
protected function sendRequest($json, $rpcId) {
$this->prepareConnection();
if (!$this->canConnect()) {
throw new XBMC_RPC_ConnectionException('Lost connection to XBMC server');
}
fwrite($this->fp, $json);
while (true) {
$result = $this->readJsonObject();
if (strpos($result, '"id" : "' . $rpcId . '"') !== false) {
break;
}
if (strpos($result, '"id":"' . $rpcId . '"') !== false) {
break;
}
}
fclose($this->fp);
return $result;
}
/**
* Checks if it is possible to connect to the server.
*
* @return bool True if it is possible to connect, false if not.
* @access private
*/
private function canConnect() {
return is_resource($this->fp);
}
/**
* Reads a single JSON object from the socket and returns it.
*
* @return string The JSON object string from the server.
* @access private
*/
private function readJsonObject() {
$open = $close = 0;
$escaping = false;
$quoteChar = null;
$result = '';
while (false !== ($char = fgetc($this->fp))) {
if (!$escaping) {
switch ($char) {
case "'":
case '"':
if (null === $quoteChar) {
$quoteChar = $char;
} elseif ($quoteChar == $char) {
$quoteChar = null;
}
break;
case '{':
if (null === $quoteChar) {
++$open;
}
break;
case '}':
if (null === $quoteChar) {
++$close;
}
break;
case '\\':
$escaping = true;
break;
}
} else {
$escaping = false;
}
$result .= $char;
if ($open == $close) {
break;
}
}
return $result;
}
}