mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-03 17:59:21 +02:00
Add XBMC Localplay
This commit is contained in:
parent
872012a24f
commit
539ca08efc
18 changed files with 1703 additions and 0 deletions
|
@ -72,6 +72,7 @@ Ampache includes some external modules that carry their own licensing.
|
|||
* [Prototype](http://www.prototypejs.org/): MIT
|
||||
* [Snoopy](http://snoopy.sourceforge.net/): LGPL v2.1
|
||||
* [Whatever:hover](http://www.xs4all.nl/~peterned): LGPL v2.1
|
||||
* [xbmc-php-rpc](https://github.com/karlrixon/xbmc-php-rpc): GPL v3
|
||||
|
||||
Translations
|
||||
------------
|
||||
|
|
633
modules/localplay/xbmc.controller.php
Normal file
633
modules/localplay/xbmc.controller.php
Normal file
|
@ -0,0 +1,633 @@
|
|||
<?php
|
||||
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
|
||||
/**
|
||||
*
|
||||
* LICENSE: GNU General Public License, version 2 (GPLv2)
|
||||
* Copyright 2001 - 2013 Ampache.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License v2
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* AmpacheXbmc Class
|
||||
*
|
||||
* This is the class for the xbmc localplay method to remote control
|
||||
* a XBMC Instance
|
||||
*
|
||||
*/
|
||||
class AmpacheXbmc extends localplay_controller {
|
||||
|
||||
/* Variables */
|
||||
private $version = '000001';
|
||||
private $description = 'Controls a XBMC instance';
|
||||
|
||||
|
||||
/* Constructed variables */
|
||||
private $_xbmc;
|
||||
// Always use player 0 for now
|
||||
private $_playerId = 0;
|
||||
// Always use playlist 0 for now
|
||||
private $_playlistId = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* This returns the array map for the localplay object
|
||||
* REQUIRED for Localplay
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
/* Do a Require Once On the needed Libraries */
|
||||
require_once Config::get('prefix') . '/modules/xbmc-php-rpc/TCPClient.php';
|
||||
|
||||
} // Constructor
|
||||
|
||||
/**
|
||||
* get_description
|
||||
* This returns the description of this localplay method
|
||||
*/
|
||||
public function get_description() {
|
||||
|
||||
return $this->description;
|
||||
|
||||
} // get_description
|
||||
|
||||
/**
|
||||
* get_version
|
||||
* This returns the current version
|
||||
*/
|
||||
public function get_version() {
|
||||
|
||||
return $this->version;
|
||||
|
||||
} // get_version
|
||||
|
||||
/**
|
||||
* is_installed
|
||||
* This returns true or false if xbmc controller is installed
|
||||
*/
|
||||
public function is_installed() {
|
||||
|
||||
$sql = "DESCRIBE `localplay_xbmc`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
return Dba::num_rows($db_results);
|
||||
|
||||
|
||||
} // is_installed
|
||||
|
||||
/**
|
||||
* install
|
||||
* This function installs the xbmc localplay controller
|
||||
*/
|
||||
public function install() {
|
||||
|
||||
$sql = "CREATE TABLE `localplay_xbmc` (`id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , ".
|
||||
"`name` VARCHAR( 128 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||
"`owner` INT( 11 ) NOT NULL, " .
|
||||
"`host` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||
"`port` INT( 11 ) UNSIGNED NOT NULL , " .
|
||||
"`user` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||
"`pass` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL" .
|
||||
") ENGINE = MYISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
// Add an internal preference for the users current active instance
|
||||
Preference::insert('xbmc_active','XBMC Active Instance','0','25','integer','internal');
|
||||
User::rebuild_all_preferences();
|
||||
|
||||
return true;
|
||||
|
||||
} // install
|
||||
|
||||
/**
|
||||
* uninstall
|
||||
* This removes the localplay controller
|
||||
*/
|
||||
public function uninstall() {
|
||||
|
||||
$sql = "DROP TABLE `localplay_xbmc`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
// Remove the pref we added for this
|
||||
Preference::delete('xbmc_active');
|
||||
|
||||
return true;
|
||||
|
||||
} // uninstall
|
||||
|
||||
/**
|
||||
* add_instance
|
||||
* This takes key'd data and inserts a new xbmc instance
|
||||
*/
|
||||
public function add_instance($data) {
|
||||
|
||||
$sql = "INSERT INTO `localplay_xbmc` (`name`,`host`,`port`, `user`, `pass`,`owner`) " .
|
||||
"VALUES (?, ?, ?, ?, ?, ?)";
|
||||
$db_results = Dba::query($sql, array($data['name'], $data['host'], $data['port'], $data['user'], $data['pass'], $GLOBALS['user']->id));
|
||||
|
||||
return $db_results;
|
||||
|
||||
} // add_instance
|
||||
|
||||
/**
|
||||
* delete_instance
|
||||
* This takes a UID and deletes the instance in question
|
||||
*/
|
||||
public function delete_instance($uid) {
|
||||
|
||||
$sql = "DELETE FROM `localplay_xbmc` WHERE `id` = ?";
|
||||
$db_results = Dba::query($sql, array($uid));
|
||||
|
||||
return true;
|
||||
|
||||
} // delete_instance
|
||||
|
||||
/**
|
||||
* get_instances
|
||||
* This returns a key'd array of the instance information with
|
||||
* [UID]=>[NAME]
|
||||
*/
|
||||
public function get_instances() {
|
||||
|
||||
$sql = "SELECT * FROM `localplay_xbmc` ORDER BY `name`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
$results = array();
|
||||
|
||||
while ($row = Dba::fetch_assoc($db_results)) {
|
||||
$results[$row['id']] = $row['name'];
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
||||
} // get_instances
|
||||
|
||||
/**
|
||||
* update_instance
|
||||
* This takes an ID and an array of data and updates the instance specified
|
||||
*/
|
||||
public function update_instance($uid,$data) {
|
||||
|
||||
$sql = "UPDATE `localplay_xbmc` SET `host` = ?, `port` = ?, `name` = ?, `user` = ?, `pass` = ? WHERE `id` = ?";
|
||||
$db_results = Dba::query($sql, array($data['host'], $data['port'], $data['name'], $data['user'], $data['pass'], $uid));
|
||||
|
||||
return true;
|
||||
|
||||
} // update_instance
|
||||
|
||||
/**
|
||||
* instance_fields
|
||||
* This returns a key'd array of [NAME]=>array([DESCRIPTION]=>VALUE,[TYPE]=>VALUE) for the
|
||||
* fields so that we can on-the-fly generate a form
|
||||
*/
|
||||
public function instance_fields() {
|
||||
|
||||
$fields['name'] = array('description' => T_('Instance Name'),'type'=>'textbox');
|
||||
$fields['host'] = array('description' => T_('Hostname'),'type'=>'textbox');
|
||||
$fields['port'] = array('description' => T_('Port'),'type'=>'textbox');
|
||||
$fields['user'] = array('description' => T_('Username'),'type'=>'textbox');
|
||||
$fields['pass'] = array('description' => T_('Password'),'type'=>'textbox');
|
||||
|
||||
return $fields;
|
||||
|
||||
} // instance_fields
|
||||
|
||||
/**
|
||||
* get_instance
|
||||
* This returns a single instance and all it's variables
|
||||
*/
|
||||
public function get_instance($instance='') {
|
||||
|
||||
$instance = $instance ? $instance : Config::get('xbmc_active');
|
||||
|
||||
$sql = "SELECT * FROM `localplay_xbmc` WHERE `id` = ?";
|
||||
$db_results = Dba::query($sql, array($instance));
|
||||
|
||||
$row = Dba::fetch_assoc($db_results);
|
||||
|
||||
return $row;
|
||||
|
||||
} // get_instance
|
||||
|
||||
/**
|
||||
* set_active_instance
|
||||
* This sets the specified instance as the 'active' one
|
||||
*/
|
||||
public function set_active_instance($uid,$user_id='') {
|
||||
|
||||
// Not an admin? bubkiss!
|
||||
if (!$GLOBALS['user']->has_access('100')) {
|
||||
$user_id = $GLOBALS['user']->id;
|
||||
}
|
||||
|
||||
$user_id = $user_id ? $user_id : $GLOBALS['user']->id;
|
||||
|
||||
Preference::update('xbmc_active', $user_id, intval($uid));
|
||||
Config::set('xbmc_active', intval($uid), true);
|
||||
|
||||
return true;
|
||||
|
||||
} // set_active_instance
|
||||
|
||||
/**
|
||||
* get_active_instance
|
||||
* This returns the UID of the current active instance
|
||||
* false if none are active
|
||||
*/
|
||||
public function get_active_instance() {
|
||||
|
||||
|
||||
} // get_active_instance
|
||||
|
||||
public function add_url(Stream_URL $url) {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Playlist->Add(array(
|
||||
'playlistid' => $this->_playlistId,
|
||||
'item' => array('file' => $url->url)
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'add_url failed: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete_track
|
||||
* Delete a track from the xbmc playlist
|
||||
*/
|
||||
public function delete_track($track) {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Playlist->Remove(array(
|
||||
'playlistid' => $this->_playlistId,
|
||||
'position' => $track
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'delete_track failed: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // delete_track
|
||||
|
||||
/**
|
||||
* clear_playlist
|
||||
* This deletes the entire xbmc playlist.
|
||||
*/
|
||||
public function clear_playlist() {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Playlist->Clear(array(
|
||||
'playlistid' => $this->_playlistId
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'clear_playlist failed: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // clear_playlist
|
||||
|
||||
/**
|
||||
* play
|
||||
* This just tells xbmc to start playing, it does not
|
||||
* take any arguments
|
||||
*/
|
||||
public function play() {
|
||||
|
||||
try {
|
||||
// XBMC requires to load a playlist to play. We don't know if this play is after a new playlist or after pause
|
||||
// So we get current status
|
||||
$status = $this->status();
|
||||
if ($status['state'] == 'stop') {
|
||||
$this->_xbmc->Player->Open(array(
|
||||
'item' => array('playlistid' => $this->_playlistId))
|
||||
);
|
||||
} else {
|
||||
$this->_xbmc->Player->PlayPause(array(
|
||||
'playerid' => $this->_playlistId,
|
||||
'play' => true)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'play failed: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // play
|
||||
|
||||
/**
|
||||
* pause
|
||||
* This tells xbmc to pause the current song
|
||||
*/
|
||||
public function pause() {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Player->PlayPause(array(
|
||||
'playerid' => $this->_playerId,
|
||||
'play' => false)
|
||||
);
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'pause failed, is the player started? ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // pause
|
||||
|
||||
/**
|
||||
* stop
|
||||
* This just tells xbmc to stop playing, it does not take
|
||||
* any arguments
|
||||
*/
|
||||
public function stop() {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Player->Stop(array(
|
||||
'playerid' => $this->_playerId
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'stop failed, is the player started? ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // stop
|
||||
|
||||
/**
|
||||
* skip
|
||||
* This tells xbmc to skip to the specified song
|
||||
*/
|
||||
public function skip($song) {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Player->GoTo(array(
|
||||
'playerid' => $this->_playerId,
|
||||
'to' => $song
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'skip failed, is the player started?: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // skip
|
||||
|
||||
/**
|
||||
* This tells xbmc to increase the volume
|
||||
*/
|
||||
public function volume_up() {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Application->SetVolume(array(
|
||||
'volume' => 'increment'
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'volume_up failed: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // volume_up
|
||||
|
||||
/**
|
||||
* This tells xbmc to decrease the volume
|
||||
*/
|
||||
public function volume_down() {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Application->SetVolume(array(
|
||||
'volume' => 'decrement'
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'volume_down failed: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // volume_down
|
||||
|
||||
/**
|
||||
* next
|
||||
* This just tells xbmc to skip to the next song
|
||||
*/
|
||||
public function next() {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Player->GoTo(array(
|
||||
'playerid' => $this->_playerId,
|
||||
'to' => 'next'
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'next failed, is the player started? ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // next
|
||||
|
||||
/**
|
||||
* prev
|
||||
* This just tells xbmc to skip to the prev song
|
||||
*/
|
||||
public function prev() {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Player->GoTo(array(
|
||||
'playerid' => $this->_playerId,
|
||||
'to' => 'previous'
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'prev failed, is the player started? ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // prev
|
||||
|
||||
/**
|
||||
* volume
|
||||
* This tells xbmc to set the volume to the specified amount
|
||||
*/
|
||||
public function volume($volume) {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Application->SetVolume(array(
|
||||
'volume' => $volume
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'volume failed: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // volume
|
||||
|
||||
/**
|
||||
* repeat
|
||||
* This tells xbmc to set the repeating the playlist (i.e. loop) to either on or off
|
||||
*/
|
||||
public function repeat($state) {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Player->SetRepeat(array(
|
||||
'playerid' => $this->_playerId,
|
||||
'repeat' => ($state ? 'all' : 'off')
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'repeat failed, is the player started? ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // repeat
|
||||
|
||||
/**
|
||||
* random
|
||||
* This tells xbmc to turn on or off the playing of songs from the playlist in random order
|
||||
*/
|
||||
public function random($onoff) {
|
||||
|
||||
try {
|
||||
$this->_xbmc->Player->SetShuffle(array(
|
||||
'playerid' => $this->_playerId,
|
||||
'shuffle' => $onoff
|
||||
));
|
||||
return true;
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'random failed, is the player started? ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // random
|
||||
|
||||
/**
|
||||
* get
|
||||
* This functions returns an array containing information about
|
||||
* The songs that xbmc currently has in it's playlist. This must be
|
||||
* done in a standardized fashion
|
||||
*/
|
||||
public function get() {
|
||||
|
||||
$results = array();
|
||||
|
||||
try {
|
||||
$playlist = $this->_xbmc->Playlist->GetItems(array(
|
||||
'playlistid' => $this->_playlistId,
|
||||
'properties' => array('file')
|
||||
));
|
||||
|
||||
for ($i = $playlist['limits']['start']; $i < $playlist['limits']['end']; ++$i) {
|
||||
$item = $playlist['items'][$i];
|
||||
|
||||
$data = array();
|
||||
$data['link'] = $item['file'];
|
||||
$data['id'] = $i;
|
||||
$data['track'] = $i + 1;
|
||||
|
||||
$url_data = $this->parse_url($data['link']);
|
||||
if ($url_data != null) {
|
||||
$song = new Song($url_data['oid']);
|
||||
if ($song != null) {
|
||||
$data['name'] = $song->get_artist_name() . ' - ' . $song->title;
|
||||
}
|
||||
}
|
||||
if (!$data['name']) {
|
||||
$data['name'] = $item['label'];
|
||||
}
|
||||
$results[] = $data;
|
||||
}
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'get failed: ' . $ex->getMessage(), 1);
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
||||
} // get
|
||||
|
||||
/**
|
||||
* status
|
||||
* This returns bool/int values for features, loop, repeat and any other features
|
||||
* that this localplay method supports.
|
||||
* This works as in requesting the xbmc properties
|
||||
*/
|
||||
public function status() {
|
||||
|
||||
$array = array();
|
||||
try {
|
||||
$appprop = $this->_xbmc->Application->GetProperties(array(
|
||||
'properties' => array('volume')
|
||||
));
|
||||
$array['volume'] = intval($appprop['volume']);
|
||||
|
||||
try {
|
||||
$currentplay = $this->_xbmc->Player->GetItem(array(
|
||||
'playerid' => $this->_playerId,
|
||||
'properties' => array('file')
|
||||
));
|
||||
// We assume it's playing. No pause detection support.
|
||||
$array['state'] = 'play';
|
||||
|
||||
$playprop = $this->_xbmc->Player->GetProperties(array(
|
||||
'playerid' => $this->_playerId,
|
||||
'properties' => array('repeat', 'shuffled')
|
||||
));
|
||||
$array['repeat'] = ($playprop['repeat'] != "off");
|
||||
$array['random'] = (strtolower($playprop['shuffled']) == 1) ;
|
||||
$array['track'] = $currentplay['file'];
|
||||
|
||||
$url_data = $this->parse_url($array['track']);
|
||||
$song = new Song($url_data['oid']);
|
||||
if ($song->title || $song->get_artist_name() || $song->get_album_name()) {
|
||||
$array['track_title'] = $song->title;
|
||||
$array['track_artist'] = $song->get_artist_name();
|
||||
$array['track_album'] = $song->get_album_name();
|
||||
}
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'get current item failed, player probably stopped. ' . $ex->getMessage(), 1);
|
||||
$array['state'] = 'stop';
|
||||
}
|
||||
} catch (XBMC_RPC_Exception $ex) {
|
||||
debug_event('xbmc', 'status failed: ' . $ex->getMessage(), 1);
|
||||
}
|
||||
return $array;
|
||||
|
||||
} // status
|
||||
|
||||
/**
|
||||
* connect
|
||||
* This functions creates the connection to xbmc and returns
|
||||
* a boolean value for the status, to save time this handle
|
||||
* is stored in this class
|
||||
*/
|
||||
public function connect() {
|
||||
|
||||
$options = self::get_instance();
|
||||
try {
|
||||
$this->_xbmc = new XBMC_RPC_TCPClient($options);
|
||||
return true;
|
||||
} catch (XBMC_RPC_ConnectionException $ex) {
|
||||
debug_event('xbmc', 'xbmc connection failed: ' . $ex->getMessage(), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // connect
|
||||
|
||||
} //end of AmpacheXbmc
|
||||
|
||||
?>
|
327
modules/xbmc-php-rpc/Client.php
Normal file
327
modules/xbmc-php-rpc/Client.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
7
modules/xbmc-php-rpc/ClientException.php
Normal file
7
modules/xbmc-php-rpc/ClientException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once 'Exception.php';
|
||||
|
||||
class XBMC_RPC_ClientException extends XBMC_RPC_Exception {
|
||||
|
||||
}
|
86
modules/xbmc-php-rpc/Command.php
Normal file
86
modules/xbmc-php-rpc/Command.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
7
modules/xbmc-php-rpc/CommandException.php
Normal file
7
modules/xbmc-php-rpc/CommandException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once 'Exception.php';
|
||||
|
||||
class XBMC_RPC_CommandException extends XBMC_RPC_Exception {
|
||||
|
||||
}
|
7
modules/xbmc-php-rpc/ConnectionException.php
Normal file
7
modules/xbmc-php-rpc/ConnectionException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once 'Exception.php';
|
||||
|
||||
class XBMC_RPC_ConnectionException extends XBMC_RPC_Exception {
|
||||
|
||||
}
|
5
modules/xbmc-php-rpc/Exception.php
Normal file
5
modules/xbmc-php-rpc/Exception.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
class XBMC_RPC_Exception extends Exception {
|
||||
|
||||
}
|
136
modules/xbmc-php-rpc/HTTPClient.php
Normal file
136
modules/xbmc-php-rpc/HTTPClient.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
7
modules/xbmc-php-rpc/InvalidCommandException.php
Normal file
7
modules/xbmc-php-rpc/InvalidCommandException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once 'Exception.php';
|
||||
|
||||
class XBMC_RPC_InvalidCommandException extends XBMC_RPC_Exception {
|
||||
|
||||
}
|
7
modules/xbmc-php-rpc/InvalidNamespaceException.php
Normal file
7
modules/xbmc-php-rpc/InvalidNamespaceException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once 'Exception.php';
|
||||
|
||||
class XBMC_RPC_InvalidNamespaceException extends XBMC_RPC_Exception {
|
||||
|
||||
}
|
166
modules/xbmc-php-rpc/Namespace.php
Normal file
166
modules/xbmc-php-rpc/Namespace.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
7
modules/xbmc-php-rpc/RequestException.php
Normal file
7
modules/xbmc-php-rpc/RequestException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once 'Exception.php';
|
||||
|
||||
class XBMC_RPC_RequestException extends XBMC_RPC_Exception {
|
||||
|
||||
}
|
66
modules/xbmc-php-rpc/Response.php
Normal file
66
modules/xbmc-php-rpc/Response.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
7
modules/xbmc-php-rpc/ResponseException.php
Normal file
7
modules/xbmc-php-rpc/ResponseException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once 'Exception.php';
|
||||
|
||||
class XBMC_RPC_ResponseException extends XBMC_RPC_Exception {
|
||||
|
||||
}
|
88
modules/xbmc-php-rpc/Server.php
Normal file
88
modules/xbmc-php-rpc/Server.php
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
7
modules/xbmc-php-rpc/ServerException.php
Normal file
7
modules/xbmc-php-rpc/ServerException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
require_once 'Exception.php';
|
||||
|
||||
class XBMC_RPC_ServerException extends XBMC_RPC_Exception {
|
||||
|
||||
}
|
139
modules/xbmc-php-rpc/TCPClient.php
Normal file
139
modules/xbmc-php-rpc/TCPClient.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue