mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-03 17:59:21 +02:00
327 lines
No EOL
11 KiB
PHP
327 lines
No EOL
11 KiB
PHP
<?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();
|
|
}
|
|
|
|
} |