PageRenderTime 50ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/xbmc-php-rpc/Client.php

https://gitlab.com/x33n/ampache
PHP | 327 lines | 136 code | 23 blank | 168 comment | 16 complexity | e8c94277b247f7df0ce4b9e5124e93a3 MD5 | raw file
  1. <?php
  2. require_once 'ClientException.php';
  3. require_once 'ConnectionException.php';
  4. require_once 'RequestException.php';
  5. require_once 'ResponseException.php';
  6. require_once 'Server.php';
  7. require_once 'Namespace.php';
  8. require_once 'Response.php';
  9. abstract class XBMC_RPC_Client {
  10. /**
  11. * @var XBMC_RPC_Server A server object instance representing the server
  12. * to be used for remote procedure calls.
  13. * @access protected
  14. */
  15. protected $server;
  16. /**
  17. * @var XBMC_RPC_Namespace The root namespace instance.
  18. * @access private
  19. */
  20. private $rootNamespace;
  21. /**
  22. * @var bool A flag to indicate if the JSON-RPC version is legacy, ie before
  23. * the XBMC Eden updates. This can be used to determine the format of commands
  24. * to be used with this library, allowing client code to support legacy systems.
  25. * @access private
  26. */
  27. private $isLegacy = false;
  28. /**
  29. * Constructor.
  30. *
  31. * Connects to the server and populates a list of available commands by
  32. * having the server introspect.
  33. *
  34. * @param mixed $parameters An associative array of connection parameters,
  35. * or a valid connection URI as a string. If supplying an array, the following
  36. * paramters are accepted: host, port, user and pass. Any other parameters
  37. * are discarded.
  38. * @exception XBMC_RPC_ConnectionException if it is not possible to connect to
  39. * the server.
  40. * @access public
  41. */
  42. public function __construct($parameters) {
  43. try {
  44. $server = new XBMC_RPC_Server($parameters);
  45. } catch (XBMC_RPC_ServerException $e) {
  46. throw new XBMC_RPC_ConnectionException($e->getMessage(), $e->getCode(), $e);
  47. }
  48. $this->server = $server;
  49. $this->prepareConnection();
  50. $this->assertCanConnect();
  51. $this->createRootNamespace();
  52. }
  53. /**
  54. * Delegates any direct Command calls to the root namespace.
  55. *
  56. * @param string $name The name of the called command.
  57. * @param mixed $arguments An array of arguments used to call the command.
  58. * @return The result of the command call as returned from the namespace.
  59. * @exception XBMC_RPC_InvalidCommandException if the called command does not
  60. * exist in the root namespace.
  61. * @access public
  62. */
  63. public function __call($name, array $arguments) {
  64. return call_user_func_array(array($this->rootNamespace, $name), $arguments);
  65. }
  66. /**
  67. * Delegates namespace accesses to the root namespace.
  68. *
  69. * @param string $name The name of the requested namespace.
  70. * @return XBMC_RPC_Namespace The requested namespace.
  71. * @exception XBMC_RPC_InvalidNamespaceException if the namespace does not
  72. * exist in the root namespace.
  73. * @access public
  74. */
  75. public function __get($name) {
  76. return $this->rootNamespace->$name;
  77. }
  78. /**
  79. * Executes a remote procedure call using the supplied XBMC_RPC_Command
  80. * object.
  81. *
  82. * @param XBMC_RPC_Command The command to execute.
  83. * @return XBMC_RPC_Response The response from the remote procedure call.
  84. * @access public
  85. */
  86. public function executeCommand(XBMC_RPC_Command $command) {
  87. return $this->sendRpc($command->getFullName(), $command->getArguments());
  88. }
  89. /**
  90. * Determines if the XBMC system to which the client is connected is legacy
  91. * (pre Eden) or not. This is useful because the format of commands/params
  92. * is different in the Eden RPC implementation.
  93. *
  94. * @return bool True if the system is legacy, false if not.
  95. * @access public
  96. */
  97. public function isLegacy() {
  98. return $this->isLegacy;
  99. }
  100. /**
  101. * Asserts that the server is reachable and a connection can be made.
  102. *
  103. * @return void
  104. * @exception XBMC_RPC_ConnectionException if it is not possible to connect to
  105. * the server.
  106. * @abstract
  107. * @access protected
  108. */
  109. protected abstract function assertCanConnect();
  110. /**
  111. * Prepares for a connection to XBMC.
  112. *
  113. * Should be used by child classes for any pre-connection logic which is necessary.
  114. *
  115. * @return void
  116. * @exception XBMC_RPC_ClientException if it was not possible to prepare for
  117. * connection successfully.
  118. * @abstract
  119. * @access protected
  120. */
  121. protected abstract function prepareConnection();
  122. /**
  123. * Sends a JSON-RPC request to XBMC and returns the result.
  124. *
  125. * @param string $json A JSON-encoded string representing the remote procedure call.
  126. * This string should conform to the JSON-RPC 2.0 specification.
  127. * @param string $rpcId The unique ID of the remote procedure call.
  128. * @return string The JSON-encoded response string from the server.
  129. * @exception XBMC_RPC_RequestException if it was not possible to make the request.
  130. * @access protected
  131. * @link http://groups.google.com/group/json-rpc/web/json-rpc-2-0 JSON-RPC 2.0 specification
  132. */
  133. protected abstract function sendRequest($json, $rpcId);
  134. /**
  135. * Build a JSON-RPC 2.0 compatable json_encoded string representing the
  136. * specified command, parameters and request id.
  137. *
  138. * @param string $command The name of the command to be called.
  139. * @param mixed $params An array of paramters to be passed to the command.
  140. * @param string $rpcId A unique string used for identifying the request.
  141. * @access private
  142. */
  143. private function buildJson($command, $params, $rpcId) {
  144. $data = array(
  145. 'jsonrpc' => '2.0',
  146. 'method' => $command,
  147. 'params' => $params,
  148. 'id' => $rpcId
  149. );
  150. return json_encode($data);
  151. }
  152. /**
  153. * Ensures that the recieved response from a remote procedure call is valid.
  154. *
  155. * $param XBMC_RPC_Response $response A response object encapsulating remote
  156. * procedure call response data as returned from Client::sendRequest().
  157. * @return bool True of the reponse is valid, false if not.
  158. * @access private
  159. */
  160. private function checkResponse(XBMC_RPC_Response $response, $rpcId) {
  161. return ($response->getId() == $rpcId);
  162. }
  163. /**
  164. * Creates the root namespace instance.
  165. *
  166. * @return void
  167. * @access private
  168. */
  169. private function createRootNamespace() {
  170. $commands = $this->loadAvailableCommands();
  171. $this->rootNamespace = new XBMC_RPC_Namespace('root', $commands, $this);
  172. }
  173. /**
  174. * Generates a unique string to be used as a remote procedure call ID.
  175. *
  176. * @return string A unique string.
  177. * @access private
  178. */
  179. private function getRpcId() {
  180. return uniqid();
  181. }
  182. /**
  183. * Retrieves an array of commands by requesting the RPC server to introspect.
  184. *
  185. * @return mixed An array of available commands which may be executed on the server.
  186. * @exception XBMC_RPC_RequestException if it is not possible to retrieve a list of
  187. * available commands.
  188. * @access private
  189. */
  190. private function loadAvailableCommands() {
  191. try {
  192. $response = $this->sendRpc('JSONRPC.Introspect');
  193. } catch (XBMC_RPC_Exception $e) {
  194. throw new XBMC_RPC_RequestException(
  195. 'Unable to retrieve list of available commands: ' . $e->getMessage()
  196. );
  197. }
  198. if (isset($response['commands'])) {
  199. $this->isLegacy = true;
  200. return $this->loadAvailableCommandsLegacy($response);
  201. }
  202. $commands = array();
  203. foreach (array_keys($response['methods']) as $command) {
  204. $array = $this->commandStringToArray($command);
  205. $commands = $this->mergeCommandArrays($commands, $array);
  206. }
  207. return $commands;
  208. }
  209. /**
  210. * Retrieves an array of commands by requesting the RPC server to introspect.
  211. *
  212. * This method supports the legacy implementation of XBMC's RPC.
  213. *
  214. * @return mixed An array of available commands which may be executed on the server.
  215. * @access private
  216. */
  217. private function loadAvailableCommandsLegacy($response) {
  218. $commands = array();
  219. foreach ($response['commands'] as $command) {
  220. $array = $this->commandStringToArray($command['command']);
  221. $commands = $this->mergeCommandArrays($commands, $array);
  222. }
  223. return $commands;
  224. }
  225. /**
  226. * Converts a dot-delimited command name to a multidimensional array format.
  227. *
  228. * @return mixed An array representing the command.
  229. * @access private
  230. */
  231. private function commandStringToArray($command) {
  232. $path = explode('.', $command);
  233. if (count($path) === 1) {
  234. $commands[] = $path[0];
  235. continue;
  236. }
  237. $command = array_pop($path);
  238. $array = array();
  239. $reference =& $array;
  240. foreach ($path as $i => $key) {
  241. if (is_numeric($key) && intval($key) > 0 || $key === '0') {
  242. $key = intval($key);
  243. }
  244. if ($i === count($path) - 1) {
  245. $reference[$key] = array($command);
  246. } else {
  247. if (!isset($reference[$key])) {
  248. $reference[$key] = array();
  249. }
  250. $reference =& $reference[$key];
  251. }
  252. }
  253. return $array;
  254. }
  255. /**
  256. * Recursively merges the supplied arrays whilst ensuring that commands are
  257. * not duplicated within a namespace.
  258. *
  259. * Note that array_merge_recursive is not suitable here as it does not ensure
  260. * that values are distinct within an array.
  261. *
  262. * @param mixed $base The base array into which $append will be merged.
  263. * @param mixed $append The array to merge into $base.
  264. * @return mixed The merged array of commands and namespaces.
  265. * @access private
  266. */
  267. private function mergeCommandArrays(array $base, array $append) {
  268. foreach ($append as $key => $value) {
  269. if (!array_key_exists($key, $base) && !is_numeric($key)) {
  270. $base[$key] = $append[$key];
  271. continue;
  272. }
  273. if (is_array($value) || is_array($base[$key])) {
  274. $base[$key] = $this->mergeCommandArrays($base[$key], $append[$key]);
  275. } elseif (is_numeric($key)) {
  276. if (!in_array($value, $base)) {
  277. $base[] = $value;
  278. }
  279. } else {
  280. $base[$key] = $value;
  281. }
  282. }
  283. return $base;
  284. }
  285. /**
  286. * Executes a remote procedure call using the supplied command name and parameters.
  287. *
  288. * @param string $command The full, dot-delimited name of the command to call.
  289. * @param mixed $params An array of parameters to be passed to the called method.
  290. * @return mixed The data returned from the response.
  291. * @exception XBMC_RPC_RequestException if it was not possible to make the request.
  292. * @exception XBMC_RPC_ResponseException if the response was not being properly received.
  293. * @access private
  294. */
  295. private function sendRpc($command, $params = array()) {
  296. $rpcId = $this->getRpcId();
  297. $json = $this->buildJson($command, $params, $rpcId);
  298. $response = new XBMC_RPC_Response($this->sendRequest($json, $rpcId));
  299. if (!$this->checkResponse($response, $rpcId)) {
  300. throw new XBMC_RPC_ResponseException('JSON RPC request/response ID mismatch');
  301. }
  302. return $response->getData();
  303. }
  304. }