Current File : //opt/RZphp72/includes/Net/MPD/Common.php |
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Music Player Daemon API
*
* PHP Version 5
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*
* @category Networking
* @package Net_MPD
* @author Graham Christensen <graham.christensen@itrebal.com>
* @copyright 2006 Graham Christensen
* @license http://www.opensource.org/licenses/mit-license.php MIT License
* @version CVS: $ID:$
*/
/**
* API for the common peices of Music Player Daemon commands
*
* Used for basic interaction and output handeling, as well as
* several standard commands.
*
* @category Networking
* @package Net_MPD
* @author Graham Christensen <graham.christensen@itrebal.com>
* @copyright 2006 Graham Christensen
* @license http://www.opensource.org/licenses/mit-license.php MIT License
* @version CVS: $ID:$
*/
class Net_MPD_Common
{
//Connection & Write Errors
const CONNECTION_NOT_OPENED = 100;
const CONNECTION_FAILED = 102;
const WRITE_FAILED = 103;
//MPD Errors
const ACK_NOT_LIST = 1;
const ACK_ARG = 2;
const ACK_PASSWORD = 3;
const ACK_PERMISSION = 4;
const ACK_UNKOWN = 5;
const ACK_NO_EXIST = 50;
const ACK_PLAYLIST_MAX = 51;
const ACK_SYSTEM = 52;
const ACK_PLAYLIST_LOAD = 53;
const ACK_UPDATE_ALREADY = 54;
const ACK_PLAYER_SYNC = 55;
const ACK_EXIST = 56;
const ACK_COMMAND_FAILED = -100;
//MPD Responces
const RESPONSE_OK = 'OK';
private $_connection = null;
protected $_errors = array();
private $_current_error = array();
protected $_commands = array();
protected $_output = array();
private $connection_params = array();
/**
* Set connection params
*
* @param $host host to connect to, (default: localhost)
* @param $port port to connec through, (default: 6600)
* @param $password password to send, (default: null)
* @return void
*/
function __construct($host = 'localhost', $port = 6600, $password = null)
{
$this->connection_params['host'] = $host;
$this->connection_params['port'] = $port;
$this->connection_params['password'] = $password;
}
/**
* Connect to MPD
*
* @return bool
*/
public function connect()
{
if ($this->isConnected()) {
return true;
}
$connection = @fsockopen($this->connection_params['host'], $this->connection_params['port'], $errn, $errs, 4);
if ($connection) {
$this->_connection = $connection;
// Read from the source until its ready for commands
//$this->read();
while (!feof($this->_connection)) {
$line = fgets($this->_connection);
if (trim(substr($line, 0, 2)) == self::RESPONSE_OK) {
break;
}
}
if (!is_null($this->connection_params['password'])) {
if ($this->runCommand('password', $this->connection_params['password']) === self::ACK_PASSWORD) {
throw new PEAR_Exception('Password invalid.', self::ACK_PASSWORD);
}
}
return true;
}
throw new PEAR_Exception('Error connecting: '.$errn.' ; '.$errs, self::CONNECTION_FAILED);
}
/**
* Check connection status
*
* @return bool
*/
public function isConnected()
{
if (!is_resource($this->_connection)) {
return false;
}
return true;
}
/**
* Disconnect from MPD
*
* @return bool
*/
public function disconnect()
{
$this->runCommand('close');
fclose($this->_connection);
$this->_connection = null;
return true;
}
/**
* Write data to the socket
*
* @param $command string data to be sent
*
* @return bool
*
*/
function write($data)
{
//Are we connected?
if (!$this->isConnected()) {
// Try to connect
$this->connect();
}
//Write the data
if (!fwrite($this->_connection, $data."\r\n")) {
throw new PEAR_Exception('Write failed', self::WRITE_FAILED);
}
$this->_commands[] = $data;
return true;
}
/**
* Read data from the socket
*
* @return array of raw output
*
*/
function read()
{
//Are we connected?
if (!$this->isConnected()) {
throw new PEAR_Exception('Not connected', self::CONNECTION_NOT_OPENED);
}
//Loop through the connection, putting the data into $line
$output = array();
while (!feof($this->_connection)) {
$line = fgets($this->_connection);
if (preg_match('/^ACK \[(.*?)\@(.*?)\] \{(.*?)\} (.*?)$/', $line, $matches)) {
//If the output is an ACK error
$this->runCommand('clearerror'); //Cleanup the error
$this->_errors[] = $matches;
$this->_current_error = array('ack' => $matches[1], 'func' => $matches[3], 'error' => $matches[4]);
throw new PEAR_Exception('Command Failed', self::ACK_COMMAND_FAILED);
} elseif (trim($line) == self::RESPONSE_OK) {
//The last line of output was hit, close the loop
break;
} else {
//Output from the server added to the return array
$output[] = $line;
}
}
return $output;
}
/**
* Get the current error data
*
* @return array of error data
*/
public function getErrorData()
{
return $this->_current_error;
}
/**
* Run command
*
* @param $command string a command to be executed through MPD
* @param $args mixed string for a single argument, array for multiple
* @param $parse mixed false to parse the output, int for parse style
*
* @return array of server output
*/
public function runCommand($command, $args = array(), $parse = 0)
{
//Generate the command
if (is_array($args)) {
foreach($args as $arg) {
$command.= ' "'.str_replace('"', '\"', $arg) .'"';
}
} elseif (!is_null($args)) {
$command.= ' "'.str_replace('"', '\"', $args) .'"';
}
//Write and then capture the output
$this->write($command);
$output = $this->read();
$this->_output[] = array($command, $output);
if ($output === array()) {
return true;
}
if ($parse !== false) {
return $this->parseOutput($output, $parse);
}
return $output;
}
/**
* Parse MPD output on a line-by-line basis
* creating output that is easy to work with
*
* @param $input array of input from MPD
* @param $style int style number,'0' for the "intelligent" sorting
*
* @return array
*/
public function parseOutput($input, $style = 0)
{
if (!is_array($input)) {
$input = array($input);
}
$count = array('outputs' => -1, 'file' => -1, 'key' => 0);
$used_keys = array();
$output = array();
$prev = array('key' => null, 'value' => null);
$dirtoggle = false;
foreach($input as $line) {
if (is_array($line)) {
$this->_errors[] = 'Server output not expected: '.print_r($line, true);
continue;
} else {
$parts = explode(': ', $line, 2);
if (!isset($parts[0], $parts[1])) {
$this->errors[] = 'Server output not expected: '.$line;
continue;
}
}
$key = trim($parts[0]);
$value = trim($parts[1]);
if ($style == 0) {
switch ($key) {
//The following has to do strictly
//with files in the output
case 'file':
case 'Artist':
case 'Album':
case 'Title':
case 'Track':
case 'Name':
case 'Genre':
case 'Date':
case 'Composer':
case 'Performer':
case 'Comment':
case 'Disc':
case 'Id':
case 'Pos':
case 'Time':
if ($key == 'file') {
$count['file']++;
}
$output['file'][$count['file']][$key] = $value;
break;
//The next section is for a 'stats' call
case 'artists':
case 'albums':
case 'songs':
case 'uptime':
case 'playtime':
case 'db_playtime':
case 'db_update':
$output['stats'][$key] = $value;
break;
//Now for a status call:
case 'volume':
case 'repeat':
case 'random':
case 'playlistlength':
case 'xfade':
case 'state':
case 'song':
case 'songid':
case 'time':
case 'bitrate':
case 'audio':
case 'updating_db':
$output['status'][$key] = $value;
break;
//Outputs
case 'outputid':
case 'outputname':
case 'outputenabled':
if ($key == 'outputid') {
$count['outputs']++;
}
$output['outputs'][$count['outputs']][$key] = $value;
if ($key == 'outputid') {
$count['outputs']++;
}
break;
//The 'playlist' case works in 2 scenarios
//1) in a file/directory listing
//2) in a status call
// This is to determine if it was in a status call
// or in a directory call.
case 'playlist':
if ($prev['key'] == 'random') {
$output['status'][$key] = $value;
} else {
$output[$key][] = $value;
}
break
;
//Now that we've covered most of the weird
//options of output,
//lets cover everything else!
default:
if (isset($used_keys[$key])) {
$used_keys = array();
$count['key']++;
}
$used_keys[$key] = true;
//$output[$count['key']][$key] = $value;//This is rarely useful
$output[$key][] = $value;
break;
}
} elseif ($style == 1) {
$output[$key][] = $value;
}
if ($key == 'directory') {
$dirtoggle = true;
}
$prev['key'] = $key;
$prev['value'] = $value;
}
return $output;
}
/**
* A method to access errors
*
* @return array
*/
public function getErrors()
{
return $this->_errors;
}
/**
* Used to discover commands that are not available
*
* @return array (null on no functions not being available)
*/
public function getNotCommands()
{
$cmds = $this->runCommand('notcommands');
if (!isset($cmds['command'])) {
return array();
}
return $cmds['command'];
}
/**
* Used to discover which commands are available
*
* @return array (null on no functions being available
*/
public function getCommands()
{
$cmds = $this->runCommand('commands');
if (!isset($cmds['command'])) {
return array();
}
return $cmds['command'];
}
/**
* Ping the MPD server to keep the connection running
*
* @return bool
*/
public function ping()
{
return $this->runCommand('ping');
}
/**
* Get various statistics about the MPD server
*
* @return array
*/
public function getStats()
{
$stats = $this->runCommand('stats');
if (!isset($stats['stats'])) {
return false;
}
return $stats['stats'];
}
/**
* Get the status of the MPD server
*
* @return array
*/
public function getStatus()
{
$status = $this->runCommand('status');
if (!isset($status['status'])) {
return false;
}
return $status['status'];
}
}
?>