
"""TCP Server

This module defines classes that implement a multiplexed TCP server on
SelectManager. """

from log import log
from errno import ECONNRESET, EPIPE, EWOULDBLOCK, EAGAIN, EINPROGRESS
import socket
import string
import SelectManager

class ConnectionSocket( SelectManager.BufferedChannel ):

    """ ConnectionSocket - basic TCP connection socket

    This class is designed to work with the SocketServer class, but can
    be used just as well to handle other TCP sockets, for example,
    connect()ed sockets. (You can make those with make_connection().)

    When used with SocketServer, ConnectionSocket may be used as
    the server's socket factory. """

    def __init__( s, sock, handler ):
        s.sock = sock
        SelectManager.BufferedChannel.__init__( s, handler )

    def fileno( s ):
        return s.sock.fileno()

    def make_receive( s ):
        try: return s.sock.recv( s.max_buffersize - len( s.rbuf ))
        except socket.error, descr:
            if descr[0] in ( ECONNRESET, EPIPE ): return ''
            else: raise

    def make_send( s ):
        if not s.wbuf: return
        sent = s.sock.send( s.wbuf )
        s.wbuf = s.wbuf[sent:]

    def close( s ):
        s.sock.close()

def make_connection( addr, handler ):

    """ make_connection - connect to a given address

    This routine returns a new channel, which hosts a TCP connection
    to addr (which should be a tuple of a hostname and port to
    connect to). To use the connection in the SelectManager main
    loop, you have to register the channel to the SelectManager
    (as normal), by using manager.add_channel( chan ). """

    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
    sock.setblocking( 0 )
    try:
	sock.connect( addr )
	# never reached
    except socket.error, descr:
	if descr[0] in ( EWOULDBLOCK, EAGAIN, EINPROGRESS ): pass
	else: raise
    sock.setblocking( 1 )
    conn = ConnectionSocket( sock, handler )
    handler.reg_conn( conn )
    return conn

class ServerSocket( SelectManager.SelectChannel ):

    """ ServerSocket - basic class for listening, multiplexed TCP server. """

    default_queue_length = 30

    def __init__( s, addr, handlerfactory,
		  sockfactory = ConnectionSocket ):

        """ Initial parameters for ServerSocket

        addr should be a tuple describing the binding address of the
        server, most probably something like ( '', myport ).
        sockfactory( os_level_socket, new_handler ) is called to form
        a new channel on incoming connection, and 
	handlerfactory( manager ) is called to form a new handler for
	the connection.

        Connection handlers should define the methods shown in
        ChannelHandler, and that class can be subclassed to form
        different connection handlers. """

        s.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        s.sock.bind( addr )
        s.sock.listen( s.default_queue_length )
        s.handlerfactory = handlerfactory
        s.sockfactory = sockfactory

    def fileno( s ):
        return s.sock.fileno()

    def can_handle_read( s ):
        return 1

    def can_handle_write( s ):
        return 0

    def handle_read_event( s, server ):
        log( 5, "incoming connection" )
        csock, addr = s.sock.accept()
        handler = s.handlerfactory( server )
        conn = s.sockfactory( csock, handler )
        handler.reg_conn( conn )
        server.add_channel( conn )

    def handle_expt_event( s, server ):
        raise SelectManager.UnknownStateException \
              ( "expt_event on a listening socket" )

    def close( s ):

        """ Close the server socket. """

        s.sock.close()

