"""Asynchronus socket server classes.

    by Phillip Lenhardt <philen@monkey.org>.

This module implements a simple (and overidable) asynchronus tcp
socket server. It is based partially on the interface to the
SocketServer module.

Currenltly, BaseAsyncTCPSocketServer and BaseAsyncUnixStreamSocketServer
should work. The udp and unix datagram servers need work.

This implementation of asyncronus socket servers (currently) requires
your version of python to have both the socket and select modules. I
think there may be a way to code around select for platforms where
it is unavailable.

To process data read in from a BaseSocketHandler-derived class, you
must define the processRead() method. It is probably best to
override serverServe() 

See the end of this file for an example of how to use this module.

"""

__version__ = '0.1'

import socket
import errno
import select
import sys

class BaseAsyncSocketServer:
    """Base asynchronus socket server class.

    Defaults to TCP.

    """

    addressFamily = socket.AF_INET
    socketType = socket.SOCK_STREAM
    requestQueueSize = 5
    
    def __init__(self,address,port,handler):
        """Extend, do not override"""
        self.address = address
        self.port = port
        self.handler = handler
        self.handlers = {}
        self.socket = socket.socket(self.addressFamily,self.socketType)
        self.serverSetBlocking()
        self.serverSetSockOpt()
        self.serverBind()
        self.serverListen()
    def serverSetBlocking(self):
        """May be overridden"""
        self.socket.setblocking(0)
    def serverSetSockOpt(self):
        """May be overridden"""
        try: self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        except: print 'Failed to set socket option SO_REUSEADDR'
    def serverBind(self):
        """May be overridden"""
        self.socket.bind((self.address,self.port))
    def serverListen(self):
        """May be overridden"""
        self.socket.listen(self.requestQueueSize)
    def serverServe(self):
        """May be overridden"""
        while 1:
            self.serverAccept()
            self.serverPoll()
    def serverAccept(self):
        """May be overridden"""
        try:
             self.handlers[self.handler(self,self.socket.accept())] = 1
        except socket.error, why:
            if why[0] == errno.EWOULDBLOCK: pass
            else: raise socket.error, why
    def serverPoll(self):
        """May be overridden"""
        reads = self.handlers.keys()
        writes = self.handlers.keys()
        excepts = []
        (reads,writes,excepts) = select.select(reads,writes,excepts,0)
        for handler in reads:
            try: handler.handleRead()
            except: handler.handleError()
        for handler in writes:
            try: handler.handleWrite()
            except: handler.handleError()
    def serverRemoveHandle(self,handler):
        try: del self.handlers[handler]
        except: pass


class BaseAsyncTCPSocketServer(BaseAsyncSocketServer):
    """Base asyncronus TCP socket server class."""


class BaseAsyncUDPSocketServer(BaseAsyncSocketServer):
    """Base asyncronus  UDP socket server class."""

    socketType = socket.SOCK_DGRAM
    #NEED TO BE IN BaseAsyncUDPSocketHandler class: maxPacketSize = 8192


if hasattr(socket,'AF_UNIX'):
    class BaseAsyncUnixStreamSocketServer(BaseAsyncTCPSocketServer):
        """Base asyncronus unix stream server."""

        addressFamily = socket.AF_UNIX


    class BaseAsyncUnixDatagramSocketServer(BaseAsyncUDPSocketServer):
        """Base asyncronus unix datagram server."""

        addressFamily = socket.AF_UNIX


class BaseAsyncSocketHandler:
    """Base asynchronus tcp socket handler class.


    """

    bufferSize = 1024

    def __init__(self,server,(socket,(address,port))):
        self.server = server
        self.socket = socket
        self.address = address
        self.port = port
        self.socket.setblocking(0)
        self.outBuffer = ''
        self.handleInit()
    def fileno(self):
        return self.socket.fileno()

    def handleInit(self):
        """Override to do initilizations"""
        pass
    def handleRead(self):
        try:
            self.data = self.socket.recv(self.bufferSize)
            if not self.data: self.handleClose()
            else: self.processRead()
        except socket.error, why:
            if why[0] in [errno.ECONNRESET,errno.ENOTCONN]:
                self.handleClose()
            else: raise socket.error, why
    def handleWrite(self):
        if len(self.outBuffer):
            try:
                numSent = self.socket.send(self.outBuffer[:self.bufferSize])
                self.outBuffer = self.outBuffer[numSent:]
            except socket.error, why:
                if why[0] == EWOULDBLOCK:
                    pass
                else:
                    raise socket.error, why
    def handleError(self):
        t, v, tbinfo = sys.exc_info()
        try: self_repr = repr(self)
        except:
             self_repr = '<__repr__ (self) failed for object at %0x>'%id(self)
        print 'uncaptured python exception, removing handler %s (%s:%s %s)' %(self_repr,t,v,tbinfo)
        self.handleClose()

    def handleClose(self):
        self.server.serverRemoveHandle(self)
        self.socket.close()


class BaseAsyncTCPSocketHandler(BaseAsyncSocketHandler):
    """Base asyncronus tcp socket handler."""
    pass

# Test Harness1
#  echos input
if __name__ == '__main__':
    class TestAsyncSocketHandler(BaseAsyncSocketHandler):
        def processRead(self):
            sys.stdout.write('got: ' + self.data)
            self.outBuffer = self.outBuffer + self.data
            self.data = ''
    server = BaseAsyncSocketServer('',8000,TestAsyncSocketHandler)
    server.serverServe()
