Location: PHPKode > projects > WaterSpout > controllers/locke_controller.php
<?php
/**
 * This file is part of WaterSpout.
 *
 * WaterSpout is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WaterSpout 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with WaterSpout.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @package WaterSpout
 * @author  Scott Mattocks <hide@address.com>
 * @author  Chris Lewis <hide@address.com>
 * @license http://www.gnu.org/licenses/lgpl.html LGPL License 3
 * @version SVN: $Id$
 * @link    http://www.spoutserver.com/
 */

class Locke_Controller extends Controller
{
	/**
	 * The maximum number of commands to store on the move stack.
	 *
	 * @const
	 */
	const MAX_MOVE_STACK_SIZE = 5000;

	/**
	 * A queue of users that are currently active.
	 *
	 * @access private
	 * @var    array
	 */
	static private $_presence = array();

	/**
	 * A queue of move events that have occurred.
	 *
	 * @static
	 * @access private
	 * @var    array
	 */
	static private $_commands = array();

	/**
	 * The current cursor position for this listener.
	 *
	 * @access private
	 * @var    float
	 */
	private $_cursor;

	/**
	 * The total number of WS requests made to updates.
	 *
	 * @static
	 * @access private
	 * @var    integer
	 */
	static private $_ws_requests = 0;

	/**
	 * The total number of long polling requests made to updates.
	 *
	 * @static
	 * @access private
	 * @var    integer
	 */
	static private $_lp_requests = 0;

	/**
	 * The total number of bytes sent for WS updates.
	 *
	 * @static
	 * @access private
	 * @var    integer
	 */
	static private $_ws_bytes_sent = 0;

	/**
	 * The total number of bytes sent for long polling updates.
	 *
	 * @static
	 * @access private
	 * @var    integer
	 */
	static private $_lp_bytes_sent = 0;

	/**
	 * The total number of bytes received for WS updates.
	 *
	 * @static
	 * @access private
	 * @var    integer
	 */
	static private $_ws_bytes_received = 0;

	/**
	 * The total number of bytes received for long polling updates.
	 *
	 * @static
	 * @access private
	 * @var    integer
	 */
	static private $_lp_bytes_received = 0;

	/**
	 * The deadline for killing off Lockes.
	 *
	 * @access protected
	 * @var    integer
	 */
	private $_presence_deadline;

	/**
	 * Constructor.
	 *
	 * @access public
	 * @return void
	 */
	public function __construct(HTTPRequest $request, Dispatcher $dispatcher)
	{
		// Call the parent constructor.
		parent::__construct($request, $dispatcher);

		// Log the request.
		$this->_log_request();
	}

	/**
	 * Adds a new move to the queue.
	 *
	 * @access public
	 * @return void
	 */
	public function moving()
	{
		// Write back the message. We do this first to close the connection and keep
		// things moving along.
		$response = new HTTPResponse(200);

		// Determine if this is a JSONP request.
		$body = array('__URI__' => $this->uri);
		if ($this->request->get_request_var('callback'))
		{
			$response->set_body($this->request->get_request_var('callback') . '(' . json_encode($body) . ');', false);
		}
		else
		{
			$response->set_body($body, true);
		}

		$this->write($response);

		if (!empty(self::$_commands))
		{
			$max = max(array_keys(self::$_commands)) + 1;
		}
		else
		{
			$max = 0;
		}

		self::$_commands[$max] = array('coords' => $this->request->get_request_var('coords'),
		                               'person' => $this->request->get_request_var('person')
		                               );

		self::$_commands = array_slice(self::$_commands, -self::MAX_MOVE_STACK_SIZE, self::MAX_MOVE_STACK_SIZE, true);
	}

	/**
	 * Adds a new message to the queue.
	 *
	 * @access public
	 * @return void
	 */
	public function talking()
	{
		// Write back the message. We do this first to close the connection and keep
		// things moving along.
		$response = new HTTPResponse(200);

		// Determine if this is a JSONP request.
		$body = array('__URI__' => $this->uri);
		if ($this->request->get_request_var('callback'))
		{
			$response->set_body($this->request->get_request_var('callback') . '(' . json_encode($body) . ');', false);
		}
		else
		{
			$response->set_body($body, true);
		}

		$this->write($response);

		if (!empty(self::$_commands))
		{
			$max = max(array_keys(self::$_commands)) + 1;
		}
		else
		{
			$max = 0;
		}

		self::$_commands[$max] = array('text'   => strip_tags($this->request->get_request_var('text')),
		                               'person' => $this->request->get_request_var('person')
		                               );

		self::$_commands = array_slice(self::$_commands, -self::MAX_MOVE_STACK_SIZE, self::MAX_MOVE_STACK_SIZE, true);
	}

	/**
	 * Listens for updates from other connections.
	 *
	 * @access public
	 * @return void
	 */
	public function updates()
	{
		// If a cursor was passed in, make that our new cursor.
		$request_cursor = $this->request->get_request_var('waterspout_cursor');
		if (empty($this->_cursor) && !empty($request_cursor) && $request_cursor <= (end(array_keys(self::$_commands)) + 1))
		{
			$this->_cursor = $request_cursor;
		}
		// If the server doesn't have any commands in the stack yet, start the
		// cursor out at 0.
		elseif (!count(self::$_commands))
		{
			$this->_cursor = 0;
		}
		// If no cursor was passed in, then figure it out.
		elseif (is_null($this->_cursor))
		{
			$this->_cursor = (int) end(array_keys(self::$_commands)) + 1;
		}

		$this->dispatcher->add_listener($this);

		if ($this->request instanceof WSRequest)
		{
			++self::$_ws_requests;
		}
	}

	/**
	 * Returns information about the number and type of requests made and the amount of
	 * data transfered.
	 *
	 * @access public
	 * @return void
	 */
	public function presence()
	{
		// Add this user to the list.
		self::$_presence[$this->request->get_request_var('person')] = array('timestamp' => time(), 'coords' => $this->request->get_request_var('coords'), 'name' => $this->request->get_request_var('name'));

		// Filter the presence array.
		$this->_presence_deadline = time() - 10;
		self::$_presence = array_filter(self::$_presence, array($this, '_presence_filter'));

		$response = new HTTPResponse(200);

		// Determine if this is a JSONP request.
		$body = array('__URI__'           => $this->uri,
					  'ws_requests'       => self::$_ws_requests,
					  'ws_bytes_received' => self::$_ws_bytes_received,
					  'ws_bytes_sent'     => self::$_ws_bytes_sent,
					  'lp_requests'       => self::$_lp_requests,
					  'lp_bytes_received' => self::$_lp_bytes_received,
					  'lp_bytes_sent'     => self::$_lp_bytes_sent,
					  'players'           => self::$_presence
					  );
		if ($this->request->get_request_var('callback'))
		{
			$response->set_body($this->request->get_request_var('callback') . '(' . json_encode($body) . ');', false);
		}
		else
		{
			$response->set_body($body, true);
		}

		$this->write($response);
	}

	/**
	 * Processes the given event.
	 *
	 * @access public
	 * @return void
	 */
	public function process_event(Controller $mover = null)
	{
		$key = array_search((int) $this->_cursor, array_keys(self::$_commands));
		if ($key === false && !is_null($this->_cursor))
		{
			return;
		}

		$commands = array_slice(self::$_commands, $key);

		if (empty($commands))
		{
			return;
		}

		$response = new HTTPResponse(200);

		$body = array('__URI__'  => $this->uri,
					  'cursor'   => end(array_keys(self::$_commands)) + 1,
					  'commands' => $commands
					  );
		if ($this->request->get_request_var('callback'))
		{
			$response->set_body($this->request->get_request_var('callback') . '(' . json_encode($body) . ');', false);
		}
		else
		{
			$response->set_body($body, true);
		}

		$this->write($response);

		$this->_cursor = (int) end(array_keys(self::$_commands)) + 1;
	}

	/**
	 * Writes the response to connection.
	 *
	 * @access public
	 * @param  HTTPResponse $response
	 * @return void
	 */
	public function write(HTTPResponse $response)
	{
		parent::write($response);

		$this->_log_response($response);
	}

	/**
	 * Logs stats for a new request.
	 *
	 * @access private
	 * @return void
	 */
	private function _log_request()
	{
		// Keep track of the total connections and how much data they sent.
		if ($this->request instanceof WSRequest)
		{
			self::$_ws_bytes_received+= $this->request->get_request_size();
		}
		else
		{
			++self::$_lp_requests;
			self::$_lp_bytes_received+= $this->request->get_request_size();
		}
	}

	/**
	 * Logs stats for a new response.
	 *
	 * @access private
	 * @param  HTTPResponse $response
	 * @return void
	 */
	private function _log_response(HTTPResponse $response)
	{
		$response->set_default_headers();
		// Tally up the total bytes sent.
		if ($this->request instanceof WSRequest)
		{
			self::$_ws_bytes_sent+= strlen($response->get_body());
		}
		else
		{
			self::$_lp_bytes_sent+= strlen($response);
		}
	}

	/**
	 * Filters out dead Lockes.
	 *
	 * @access public
	 * @return void
	 */
	public function _presence_filter($item)
	{
		return $item['timestamp'] >= $this->_presence_deadline;
	}
}
?>
Return current item: WaterSpout