<?

/*

Shadok, the aEGiS PHP Server Daemon Toolkit
===========================================

Copyright (C) 2003 Vincent Negrier aka. sIX <six@aegis-corp.org>

This library 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 2.1 of the License, or (at your option) any later version.

This library 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 this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

define("SK_VERSION", "0.7.9a");

/* TODO

- write buffers (0.8)
- udp (0.9)

*/

class SK_Socket {

	var $fd;

	function setopt($level, $optname, $optval) {

		socket_set_option($this->fd, $level, $optname, $optval);
	
	}

	function getopt($level, $optname) {

		return (socket_get_option($this->fd, $level, $optname));
	
	}
	
	function last_error() {

		return (socket_last_error($this->fd));
	
	}
	
	function set_nonblock() {

		socket_set_nonblock($this->fd);
	
	}

	function read($size) {

		return (socket_read($this->fd, $size));
	
	}

	function write($msg) {

		return (socket_write($this->fd, $msg));

	}

	function close() {

		return (socket_close($this->fd));

	}

	function getpeername() {

		$remote_ip=$remote_port=0;

		socket_getpeername($this->fd, $remote_ip, $remote_port);

		return array($remote_ip, $remote_port);
	
	}

}

class SK_Listen_Socket extends SK_Socket {

	var $accepted;
	
	function create($domain, $type, $proto) {

		$this->fd=socket_create($domain, $type, $proto);
	
	}
	
	function listen($addr, $port, $backlog=20) {

		$ret=socket_bind($this->fd, $addr, $port);
		if ($ret==false) return false;

		$ret=socket_listen($this->fd, $backlog);
		if ($ret==false) return false;

		return true;
	
	}

	function &accept() {

		if (is_resource($ret=socket_accept($this->fd))) {

			++$this->accepted;
			
			$sck =& new sk_socket();
			$sck->fd=$ret;

			return($sck);
		
		} else {
			
			return false;

		}
		
	}

}

class SK_Server {

	var $active_listeners=array();
	var $active_sessions=array();

	var $last_error;

	var $options=array();

	// options["reuse_address"] : set SO_REUSEADDR on listen sock

	function Parse_PAP($pap) {

		// Parse a "proto://addr:port" string

		$tmp=array();
		
		if (ereg("^([a-zA-Z]+)://([0-9\.]+):([0-9]+)$", $pap, $tmp)) {

			return array("proto" => strtolower($tmp[1]), "addr" => $tmp[2], "port" => $tmp[3]);
		
		} else {

			return false;
		
		}
	
	}

	function Set_Option($name, $value) {

		$this->options[strtolower($name)]=$value;

	}

	function Register_Connection_Handler($pap, $connection_handler, $listen_backlog=20) {

		$pap=strtolower($pap);
		$p=sk_server::parse_pap($pap);

		if (is_array($this->active_listeners[$pap])) {

			$this->last_error=$pap." is already registered";
			return false;
		
		}
		
		if ($p["proto"]!="tcp") {

			$this->last_error="connection handler is only for TCP sockets";
			return false;
		
		}

		$s =& new sk_listen_socket();

		$s->create(AF_INET, SOCK_STREAM, SOL_TCP);
		
		if (!is_resource($s->fd)) {

			$this->error="can not create socket";
			return false;
		
		}

		if ($this->options["reuse_address"]) $s->setopt(SOL_SOCKET, SO_REUSEADDR, 1);
		$s->set_nonblock();
		
		$b=$s->listen($p["addr"], $p["port"], $listen_backlog);

		if ($b==false) {

			$this->error=$s->last_error();
			return false;
		
		}

		$this->active_listeners[$pap] =& new SK_Connection_Listener($pap, $s, $connection_handler);

		return true;

	}

	function Get_All_Sockets() {

		$allsocks=array();
		
		foreach ($this->active_listeners as $al) $allsocks[]=$al->socket->fd;
		foreach ($this->active_sessions as $as) $allsocks[]=$as->socket->fd;

		return ($allsocks);
	
	}
	
	function Broadcast($bcast) {
		
		foreach ($this->active_sessions as $as) $as->socket->write($bcast);

	}
	
	function &Is_Active_Listener($rsock) {

		foreach ($this->active_listeners as $key=>$al) if ($al->socket->fd===$rsock) return ($this->active_listeners[$key]);
		return false;
	
	}
	
	function &Is_Active_Session($rsock) {

		foreach ($this->active_sessions as $key=>$as) if ($as->socket->fd===$rsock) return ($this->active_sessions[$key]);
		return false;
	
	}
	
	function Run($loops=false) {
		
		$nb_loops=0;
		
		while (($loops===false) || (++$nb_loops<=$loops)) {

			$rsocks=$this->Get_All_Sockets();
			$wsocks=NULL;
			$dummy=NULL;

			if (@socket_select($rsocks, $wsocks, $dummy, 1)) {

				foreach ($rsocks as $rsock) {
					
					if ($al =& $this->Is_Active_Listener($rsock)) {

						$pap=$al->pap;
						$s=$al->socket->accept();
						$conn_id=$pap."#".$this->active_listeners[$pap]->socket->accepted;

						list($remote_ip, $remote_port)=$s->getpeername();
						
						$hnd =& new $al->connection_handler;
						$hnd->conn_id=$conn_id;
						$hnd->server=&$this;
						$hnd->socket=$s;
						$this->active_sessions[$conn_id] =& $hnd;
						$this->active_sessions[$conn_id]->on_connect();

					} else if ($as =& $this->Is_Active_Session($rsock)) {

						$buf=$as->socket->read(4096);

						if ($buf==="") {

							// reset by peer

							$as->on_disconnect();
							unset($this->active_sessions[$as->conn_id]);
						
						} else {

							$as->on_read($buf);
						
						}
					
					}

				}
			
			}
		
		}
	
	}

}

class SK_Connection_Listener {

	var $pap;
	var $socket;
	var $connection_handler;

	function SK_Connection_Listener($pap, &$socket, $connection_handler) {

		$this->pap=$pap;
		$this->socket =& $socket;
		$this->connection_handler=$connection_handler;
	
	}

}

class SK_Connection_Handler {

	var $conn_id;
	var $server;
	var $socket;
	
	function on_connect() { 
	
		// Method on_connect() is called after a client connected to the server
	
	}

	function on_disconnect() {

		// Method on_disconnect() is called after a client disconnected from the server
	
	}

	function on_read() {

		// Method on_disconnect() is called after data has been read from the client
	
	}
	
	function disconnect() {

		$this->socket->close();
		unset($this->server->active_sessions[$this->conn_id]);
	
	}

}

class SK_Line_Input_Connection_Handler extends SK_Connection_Handler {

	var $rcv_buffer;
	
	function on_read($data) {

		$this->rcv_buffer.=$data;

		if ($p=strrpos($this->rcv_buffer, "\n")) {

			$lns_arr=explode("\n", substr($this->rcv_buffer, 0, $p-1));
			$this->rcv_buffer=substr($this->rcv_buffer, $p+1);

			foreach ($lns_arr as $line) if ($s=trim($line)) $this->on_read_line($s);
		
		}

	}
	
	function on_read_line($data) {

		// Method on_read_line() is called for each line (\n terminated string) read from the client
	
	}
	
}

class SK_Buffered_Write {

	var $fd;
	var $send_buffer;
	var $callback_object;
	var $callback_method;

	// FIXME

}

class SK_Toolkit {

	function techo($str, $filename=false) {

		$tl=date("Ymd:His")." $str\n";

		if (($filename) && ($f=@fopen($filename, "a"))) {

			fwrite($f, $tl);
			fclose($f);
		
		} else {

			echo $tl;
			flush();
		
		}
	
	}
	
	function Run_In_Background($err_code=0) {

		if (is_callable("posix_setsid") && is_callable("pcntl_fork")) {
		
			posix_setsid();
			if (($pid=pcntl_fork())===0) exit($err_code);

			return ($pid);
		
		} else {

			return false;
		
		}

	}

}

?>