Description:
This was written for an undergraduate course in Network Programming.

This particular piece of code was written in Python to implement a peer-to-peer instant messenger.

"""
Project 01 - TCP based P2P messaging program
EECE 4275 - Network Programming
@author Jon Olson
@version 0.5.0
@requires Python 2.7, Cerealizer
Cerealizer can be downloaded from http://home.gna.org/oomadness/en/cerealizer/index.html
"""


import SocketServer
import re
import cerealizer
import threading
import sys
import logging


class BaseTransmission:
    """
    A generic transmission between any two hosts
    """
   
    def __init__( self, **kwargs ):
        """
        A utility method for subclasses to use to quickly set the payload dictionary. These
        transmissions are initialized on the sending side.
        @param kwargs a dictionary of variable length
        """
        self.payload = kwargs
       
    def process( self, HOST = None, PORT = None ):
        """
        A dummy method for subclasses to override. This method is called on the receiving side
        of the transmission.
        @param HOST the address of the person sending the message
        @param PORT the port the HOST is transmitting from, but not necessarily listening to. The
        payload often contains information regarding the port being listened to.
        @return If something is returned from this method, it will be sent back to the address
        sending this transmission.
        """
        pass
       

class Acknowledge( BaseTransmission ):
    """
    An acknowledgement to send on receipt of various communications.
    """
   
    pass
   
   
class PeerMessage( BaseTransmission ):
    """
    A message sent between peers
    """
   
    def __init__( self, fromUser, message ):
        """
        @param fromUser The peer (name) initiating the transmission.
        @param message The content of what the user wishes to say.
        """
        BaseTransmission.__init__(self, name = fromUser, message = message)
   
    def process( self, HOST = None, PORT = None ):
        """
        This implementation simply calls the receiving client's method for handling this event.
        """
        global clientApp
        clientApp.peerMessage( self.payload['name'], self.payload['message'] )
       
        return Acknowledge()
       
       
class PeerBroadcast( PeerMessage ):
    """
    A special kind of message being transmitted to all peers.
    """
   
    def process( self, HOST = None, PORT = None ):
        """
        This implementation simply calls the receiving client's method for handling this event.
        """
        global clientApp
        clientApp.peerBroadcast( self.payload['name'], self.payload['message'] )
       
        return Acknowledge()
   
   
class NameConflict( BaseTransmission ):
    """
    The connecting client provided a name that is either not distinct or was malformed.
    """
   
    def process( self, HOST = None, PORT = None ):
        """
        This implementation simply calls the connecting client's method for handling this event.
        """
        global clientApp
        clientApp.nameConflict()
       
        return Acknowledge()
   
   
class ClientConnect( BaseTransmission ):
    """
    Registers a client with the server
    """
   
    def __init__( self, name, ip, port ):
        """
        @param name The name of the user connecting.
        @param ip The ip of the user connecting.
        @param port The port the user will be listening on, not necessarily where this transmission
        will come from.
        """
        BaseTransmission.__init__( self, name = name, ip = ip, port = port )
   
    def process( self, HOST = None, PORT = None ):
        global clientList, args
       
        verbose( "client list:", clientList)
       
        # verify they aren't in the client list already
        if self.payload['name'] in clientList:
            verbose( "Name conflict detected in client list:", self.payload['name'] )
            #Send( HOST, PORT, NameConflict() )
            return NameConflict()
        # verify the name isn't malformed
        elif re.search('\W', self.payload['name']) != None:
            verbose( "Malformed name detected:", self.payload['name'] )
            return NameConflict()
       
        # build the peer list, and notify all peers of the connection
        peers = dict()
        verbose( self.payload )
        c = PeerConnect( self.payload['name'], self.payload['ip'], self.payload['port'] )
        for client in clientList:
            peers[client] = {'ip': clientList[client]['ip'], 'port': clientList[client]['port']}
            verbose( "notifying", client, clientList[client] )
            ack = SendRespond( clientList[client]['ip'], clientList[client]['port'], c )
            if not isinstance( ack, Acknowledge ):
                verbose( "did not get an acknowledgement from", client )
       
        # add them to the client list
        clientList[self.payload['name']] = {
            'ip': self.payload['ip'],
            'port': self.payload['port'],
            'tick': 0,
            }
        verbose(self.payload['name'], "has logged in.")

        # respond with the peer list       
        return peers
       
       
class PeerConnect( ClientConnect ):
    """
    Sent from the server to existing peers when a new peer comes online
    """
   
    def process( self, HOST = None, PORT = None ):
        """
        This implementation simply calls the connecting client's method for handling this event.
        """
        global clientApp
        clientApp.peerConnect( self.payload['name'], self.payload['ip'], self.payload['port'] )
       
        return Acknowledge()
       

class PeerUpdate( BaseTransmission ):
    """
    The server detected an IP address change for a user and notifies each of the peers with this
    transmission.
    """
   
    def __init__( self, name, ip ):
        """
        @param name The name of the user being updated.
        @param ip The IP address to now use.
        """
        BaseTransmission.__init__( self, name = name, ip = ip )
       
    def process( self ):
        """
        This implementation simply calls the connecting client's method for handling this event.
        """
        global clientApp
        clientApp.peerUpdate( self.payload['name'], self.payload['ip'] )
       
        return Acknowledge()
   
   
class ClientMaintain( BaseTransmission ):
    """
    Maintain the presence of client information with the server. Used by the server to detect when
    a user disconnect's for unknown reasons.
    """
   
    def __init__( self, name ):
        """
        @param name The user this transmission pertains to.
        """
        self.payload = name
   
    def process( self, HOST = None, PORT = None ):
        """
        Reset the client list's stale counter for the user.
        """
        global clientList, args
        if self.payload in clientList:
            verbose( "Found the client" )
            clientList[self.payload]['tick'] = 0
            #if clientList[self.payload]['ip'] != HOST:
            #    verbose( "updating peers with new ip address" )
            #    for x in clientList:
            #        if x != self.payload:
            #            Send( clientList[x]['ip'], clientList[x]['port'], PeerUpdate( self.payload, HOST ) )
            #    clientList[self.payload]['ip'] = HOST
        else:
            verbose( "Didn't find the client" )
       
        return Acknowledge()
   

class ClientDisconnect( BaseTransmission ):
    """
    This message is sent by a client to the server when they actively disconnect.
    """
   
    def __init__( self, name ):
        """
        @param name The name of the user disconnecting
        """
        self.payload = name

    def process( self, HOST = None, PORT = None ):
        """
        Attempt to delete the user from the client list, then update all peers of the disconnect.
        """
        global clientList
        try:
            del clientList[self.payload]
            verbose(self.payload, "has been disconnected from the server.")
            p = PeerDisconnect( self.payload )
            for x in clientList:
                verbose( "Notifying", x, "of disconnect." )
                ack = SendRespond( clientList[x]['ip'], clientList[x]['port'], p )
                if not isinstance( ack, Acknowledge ):
                    verbose( "did not get an acknowledgement from", x )
        except KeyError:
            pass
           
        return Acknowledge()
       

class PeerDisconnect( ClientDisconnect ):
    """
    Sent by the server to all existing peers when a client disconnects (actively or not).
    """

    def process( self, HOST = None, PORT = None ):
        """
        This implementation simply calls the connecting client's method for handling this event.
        """
        global clientApp
        clientApp.peerDisconnect( self.payload )
       
        return Acknowledge()
   
   
def TickClients():
    """
    The server uses a tick counting mechanism to automatically disconnect clients after a certain
    elapsed time
    """
   
    global clientList, tik
   
    tik = not tik
    if tik:
        verbose( "tik" )
    else:
        verbose( "tok" )

    for client in clientList:
        try:
            clientList[client]['tick'] += 1
        except KeyError:
            pass
           
           
def TrimClients():
    """
    Trim all clients who are deemed stale
    """
   
    global clientList
   
    verbose( "Trimming", len(clientList), "clients..." )
    for client in clientList.keys():
        try:
            verbose("client:", client, "tick:", clientList[client]['tick'])
            # check if the tick count is "stale"
            if clientList[client]['tick'] > 1:
                verbose( "Disconnecting", client )
                c = PeerDisconnect( client )
                # only tell other users who are not "stale"
                for x in clientList:
                    if clientList[x]['tick'] <= 1:
                        ack = SendRespond( clientList[x]['ip'], clientList[x]['port'], c )
                        if not isinstance( ack, Acknowledge ):
                            verbose( "did not get an acknowledgement from", x )
                del clientList[client]
        except AttributeError:
            pass
   
    # automatically shut down the server when there are no more clients
    if len(clientList) == 0:
        verbose( "No more clients detected, shutting down server..." )
        global ShutdownEvent
        ShutdownEvent.set()
       
       
def Send( HOST, PORT, payload ):
    """
    Send a given transmission to a given address
    @param HOST host address
    @param PORT port to send data to
    @param payload the data to serialize into a stream for transfer
    """
   
    # check if there's nothing to send, if so, don't send anything.
    if payload == None:
        return
       
    verbose( "attempting to send something to", HOST, PORT, type(payload) )
   
    import socket
    try:
        # create a new socket just for this transmission
        sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        sock.connect( (HOST, PORT) )
       
        # scramble the transmission, not necessarily for security, but for simplicity of handling
        # the transmission of data on the receiving end
        sock.send( scramble( payload ) )
        sock.close()
    except socket.error as e:
        errno, string = e.args
        # swallow connection refusal errors
        if errno != 61:
            # this error was not a connection refusal, so let it bubble up to the next level
            raise
        else:
            verbose( "swallowed:", errno, string )

       
def SendRespond( HOST, PORT, payload ):
    """
    Send a given transmission to a given address, expecting a response
    @param HOST host address
    @param PORT port to send data to
    @param payload the data to serialize into a stream for transfer
    @return a response from the recipient of the message
    """
   
    if payload == None:
        return
   
    verbose( "attempting to send something to", HOST, PORT, type(payload), "and waiting for a response" )
   
    import socket
    try:
        #t = socket.gettimeout()
        #socket.settimeout(None)

        # create a new socket just for this transmission
        sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        sock.connect( (HOST, PORT) )

        # scramble the transmission, not necessarily for security, but for simplicity of handling
        # the transmission of data on the receiving end
        sock.send( scramble( payload ) )
        r = unscramble(socketReadline( sock ))
        sock.close()
        #socket.settimeout(t)
        verbose( "got response:", r )
        return r
    except socket.timeout:
        sock.close()
        return -1
    except socket.error as e:
        errno, string = e.args
        # swallow connection refusal errors
        if errno != 61:
            # this error was not a connection refusal, so let it bubble up to the next level
            raise
        else:
            verbose( "swallowed:", errno, string )
            return -1
   
   
def Respond( payload ):
    """
    Serialize the given payload, whatever it is. This may cause an exception to be raised if the
    payload's type is not registered with cerealizer. This function was intended to be used for
    responding during a transmission handling. It is not being used at this time, however, opting
    for SendRespond(), and the specifics of implementation in the process() method of the
    transmission object.
    @param payload The data to serialize
    @deprecated use SendRespond() and a subclass of BaseTransmission's process() method.
    """
    return cerealizer.dumps( payload )
           

def Tick():
    """
    Perform periodic maintainence on the client list.
    """
   
    # increase the "staleness" of all clients
    TickClients()
   
    # disconnect any clients who are now "stale"
    TrimClients()
   
    # restart the timer to call this function again.
    global t
    t = threading.Timer( 20, Tick )
    t.start()
   
   
def Alive():
    """
    Notify the server that the client is still alive.
    @deprecated ClientApp has a method called maintain() to handle this now.
    """
   
    global myName, myIP, myPort, args, tik

    tik = not tik
    if tik:
        verbose( "tik" )
    else:
        verbose( "tok" )

    c = ClientMaintain()
    c.payload = Peer()
    c.payload.name = myName
    c.payload.ip = myIP
    c.payload.port = myPort
    Send( args.host, args.port, c )
   
    global t
    t = threading.Timer( 15, Alive )
    t.start()
   
   
def verbose( *vararg ):
    """
    Used to log verbose debug information
    """
   
    # create a string based list of the arguments to this function
    new = list()
    for arg in vararg:
        new.append(str(arg))
   
    logging.debug( ' '.join(new) )
   
   
class BaseServerHandler( SocketServer.BaseRequestHandler ):
    """
    General handler for server interactions
    """
   
    def handle( self ):
        """
        Handle a server event generically
        """
        verbose("beginning handling of server event...")
        global args
       
        # extract an entire request and unscramble it
        o = unscramble(socketReadline( self.request ).strip())
       
        verbose( self.acceptable )
       
        # only process acceptable transmissions
        if isinstance( o, self.acceptable ):
            verbose( "Got a", o.__class__.__name__, "transmission" )
            HOST, PORT = self.client_address
            response = o.process( HOST, PORT )
           
            # if the process returned a response, send it
            if response != None:
                self.request.send(scramble(response))
        else:
            try:
                verbose( "Got an unacceptable type:", o.__class__.__name__ )
            except AttributeError:
                verbose( "Got an unknown type.", o )
        verbose("finished handling event...")
               
    def setup( self ):
        """
        This method is automatically called to setup the instance for the SocketServer being used
        """
        self.acceptable = tuple()
           
    def setAcceptable( self, acceptability ):
        """
        Set the list of acceptable classes the serialization creates
        @param acceptability A tuple of acceptable classes for this server
        """
        self.acceptable = acceptability
   
   
class ClientHandler( BaseServerHandler ):
    """
    Handle events coming into the client
    """
   
    def handle( self ):
        verbose( self.__class__.__name__, "handle" )
        BaseServerHandler.handle( self )
   
    def setup( self ):
        verbose( self.__class__.__name__, "setup" )
        BaseServerHandler.setup( self )
        self.setAcceptable( tuple( [NameConflict, PeerConnect, PeerUpdate, PeerDisconnect, PeerMessage, PeerBroadcast] ) )
   
   
class ServerHandler( BaseServerHandler ):
    """
    Handle events coming into the server
    """
   
    def handle( self ):
        verbose( self.__class__.__name__ )
        BaseServerHandler.handle( self )
   
    def setup( self ):
        BaseServerHandler.setup( self )
        self.setAcceptable( tuple( [ClientConnect, ClientMaintain, ClientDisconnect] ) )
       

class ThreadedTCPServer( SocketServer.ThreadingMixIn, SocketServer.TCPServer ):
    """
    This class specifically mixes threading support and TCP service, all implementation is handled
    inside SocketServer
    """
    pass
   

def scramble( payload ):
    """
    Scramble a given payload for transmission. A trailing carriage return is added for the readline
    function.
    @param payload The data to scramble.
    """
    import base64
    return base64.urlsafe_b64encode( cerealizer.dumps( payload ) ) + '\n'
   

def unscramble( payload ):
    """
    Reverse the results of a scrambled payload.
    @param payload The data to unscramble.
    """
    import base64
    return cerealizer.loads( base64.urlsafe_b64decode( payload.rstrip() ) )
   

def socketReadline( sock ):
    """
    Attempt to read a complete line from a given socket.
    @param sock The socket to read from.
    """
   
    line = ''
   
    # switch time out settings on the socket temporarily
    x = sock.gettimeout()
    sock.settimeout(1)
   
    try:
        verbose( 'reading a line from the socket' )
        while '\n' not in line and '\r' not in line:
            line += sock.recv(1)
        verbose( 'finished reading a line from socket' )
        sock.settimeout(x)
        return line.strip()
    except socket.timeout:
        sock.settimeout(x)
        verbose( 'socket timed out' )
        raise
   
   
class ClientApp:
    """
    The bulk of code relating to client handling and interaction
    """
   
    def __init__( self, name, ip, host, hostPort ):
        """
        Relatively simple initialization of internal variables needed throughout the client
        @param name The username to use for this client
        @param ip The ip address of the client, the port used for serving is found later
        @param host The authoritative server to use
        @param hostPort The authoritative server's port
        """
        self.name = name
        self.host = host
        self.hostPort = hostPort
        self.width = self.height = None
        self.messengerWindow = self.userWindow = None
        self.messengerThread = self.userThread = None
        self.shutdownEvent = threading.Event()
        self.timer = None
        self.commands = {
            'exit': self.quit,
            'quit': self.quit,
            'm': self.message,
            'msg': self.message,
            'users': self.users,
            'peers': self.users,
            'help': self.help,
            }
        self.commandReg = re.compile( '^\s*/(?P<command>' + '|'.join(self.commands.keys()) +
            ')\s*(?P<rest>.*)$', re.I )
        self.server = None
        self.ip = ip
        self.port = None
        self.peers = dict()
        self.reason = None
        self.error = None
       
    def start( self ):
        """
        This method starts the curses wrapper and handles internal errors
        """
        # initiate curses
        # pass off execution to run()
        import curses
        try:
            curses.wrapper( self.run )
            if self.error != None:
                raise self.error
        except self.ClientError as e:
            print e.reason
       
    def run( self, win ):
        """
        This method handles the primary running of the client
        @param win The screen, as passed by curses' wrapper function
        """
       
        import curses

        # divide the screen with a horizontal line separating user and message level areas
        self.height, self.width = win.getmaxyx()
        verbose( self.width, self.height )
        win.hline( self.height - 2, 0, curses.ACS_HLINE, self.width )
        win.refresh()
       
        # sleep for a short time to allow the window to settle before moving on
        curses.napms( 500 )
       
        # create the window for message displays
        self.messengerWindow = win.derwin( self.height - 2, self.width, 0, 0 )
        self.messengerWindow.scrollok( True )
       
        # create the window for user typing
        self.userWindow = win.derwin( 1, self.width, self.height - 1, 0 )
        self.userWindow.scrollok( True )
        self.userWindow.nodelay( 0 )
        self.userWindow.notimeout( 1 )

        # create the message handling thread
        self.server = ThreadedTCPServer( (self.ip, 0), ClientHandler )
        self.ip, self.port = self.server.server_address
        verbose( "{}'s client's server info".format(self.name), self.ip, self.port )
        self.messengerThread = threading.Thread( target = self.server.serve_forever, name = 'ClientApp.messenger' )
        self.messengerThread.start()

        # send the initial connection request to the server and get its response
        r = SendRespond( self.host, self.hostPort, ClientConnect( self.name, self.ip, self.port ) )
       
        import types       

        # if there was a name conflict
        if type(r) == types.DictType:
            self.peers = r
        else:
            if isinstance( r, NameConflict ):
                self.nameConflict()
            else:
                if type(r) == types.IntType and r == -1:
                    self.error = self.ClientError( 'server did not respond in time or refused connection' )
                elif r != None:
                    self.error = self.ClientError( 'unknown reason ' + str(r) )
                else:
                    self.error = self.ClientError( 'should have gotten a response' )
                self.shutdown()
            return
       
        # always display initial help information
        self.help()
       
        # always display peers initially
        self.users()
       
        # create the thread for user typing
        self.userThread = threading.Thread( target = self.userInteract, name = 'ClientApp.user' )
        curses.echo()
        self.userThread.start()
       
        # initiate the client maintanence timer thread
        self.maintain()
       
        # wait for the client events to allow a shut down
        self.shutdownEvent.wait()
       
        # depending on what initiated the shut down, the server may or may not be initialized
        if self.server != None:
            self.server.shutdown()
       
        # if there was a reason given for the shut down, raise an exception to start() for it.
        if self.reason != None:
            self.error = self.ClientError( self.reason )
           
    def maintain( self ):
        """
        A timer thread to maintain presence with the server
        """
        verbose( "maintaining server connection" )
        ack = SendRespond( self.host, self.hostPort, ClientMaintain( self.name ) )
        if not isinstance( ack, Acknowledge ):
            verbose( "did not get an acknowledgement from the server" )
        self.timer = threading.Timer( 10, self.maintain )
        self.timer.start()
       
    def userInteract( self ):
        """
        user interaction thread
        terminates when a command sets shutdownEvent or an error is created internally
        """
       
        while not self.shutdownEvent.is_set() and self.error == None:
            userInput = self.userWindow.getstr(0,0).rstrip()
            self.userWindow.erase()
            if userInput != "":
                verbose( "got input:", userInput )
                m = self.commandReg.match( userInput )
                if m != None:
                    verbose( "found a command:", m.group('command') )
                    # a command was found in the input
                    self.commands[m.group('command')]( m.group('rest') )
                else:
                    # broadcast to all peers
                    self.broadcast( userInput )
                   
    def quit( self, data = None ):
        """
        The quit command.
        @param data Any extra data sent to the command (which is ignored.)
        """
       
        verbose( "attempting to quit client..." )
        global args
        ack = SendRespond( args.host, args.port, ClientDisconnect( self.name ) )
        if not isinstance( ack, Acknowledge ):
            verbose( "did not get an acknowledgement from the server" )
        self.shutdown()
       
    def help( self, data = None ):
        """
        The help command.
        @param data Any extra data sent to the command (which is ignored.)
        """
       
        self.display( "Available commands:" )
        self.display( "  /" + ' /'.join(sorted(self.commands.keys())) )
       
    def message( self, data = None ):
        """
        The message command.
        @param data The remainder of the command given to the client.
        """
       
        # do nothing if no data was sent
        if data == None or data == '':
            return
       
        verbose( "attempting to send a message to user" )
        try:
            # extract the user to send the message to from the data
            user, data = data.split( None, 1 )
           
            # ensure the user is a registered peer
            if user in self.peers:
                m = PeerMessage( self.name, data )
                ack = SendRespond( self.peers[user]['ip'], self.peers[user]['port'], m )
                if not isinstance( ack, Acknowledge ):
                    verbose( "did not get an acknowledgement from", user )
                self.display( self.name + '@' + user + ': ' + data )
            else:
                self.display( "! NOTE: unknown user" )
        except ValueError:
            self.display( "! NOTE: unknown user or empty message" )
            pass
           
    def users( self, data = None ):
        """
        The users command.
        @param data Any extra data sent to the command (which is ignored.)
        """
       
        verbose( "attempting to display users..." )
       
        # check if there are any peers to display
        if len(self.peers) > 0:
            self.display( "Users:" )
            copy = dict(self.peers)
            copy[self.name] = 1
            self.display( '  ' + ' '.join(sorted(copy.keys())) )
        else:
            self.display( 'there are no other active peers' )
       
    def broadcast( self, data = None ):
        """
        The broadcast command. This is chosen by default if no other commands were matched.
        @param data The message that will be sent to all peers
        """
       
        # ensure there is something to send, first
        if data == None or data == '':
            return
           
        verbose( "attempting to broadcast a message to all peers..." )
       
        m = PeerBroadcast( self.name, data )
       
        # iterate over the peers sending the message
        for name in self.peers:
            verbose( "sending message to", name )
            ack = SendRespond( self.peers[name]['ip'], self.peers[name]['port'], m )
            if not isinstance( ack, Acknowledge ):
                verbose( "did not get an acknowledgement from", name )
           
        # show the message to the user
        self.peerBroadcast( self.name, data )
       
    def peerConnect( self, name, ip, port ):
        """
        A peer is coming online. Add them to the peer list.
        @param name The name of the user.
        @param ip Their IP address.
        @param port The port they are listening on.
        """
       
        verbose( "peer connecting", name )
        self.peers[name] = {'ip': ip, 'port': port}
        self.display( name + ' connected.' )
       
    def peerDisconnect( self, name ):
        """
        A peer is being disconnected. Remove them from the peer list.
        @param name The user's name
        """
       
        verbose( "peer disconnecting", name )
        try:
            del self.peers[name]
            self.display( name + ' disconnected.' )
        except AttributeError:
            # this should only happen if they weren't actually in the peer list, for some reason
            pass
       
    def peerMessage( self, name, message ):
        """
        Receiving a direct message from another user
        @param name The user sending the message.
        @param message Their message to this user.
        """
       
        verbose( "incoming direct message from", name + ":", message )
        self.display( name + '@' + self.name + ': ' + message )
       
    def peerUpdate( self, name, ip ):
        """
        A peer's IP address has changed, this will update the peer list accordingly.
        """
       
        verbose( "updating peer ip address" )
        if name in self.peers:
            self.peers[name]['ip'] = ip
           
    def peerBroadcast( self, name, message ):
        """
        A message is being broadcast to all peers of a user.
        @param name The user sending the broadcast.
        @param message The message they sent.
        """
       
        verbose( "incoming message from", name + ":", message )
        self.display( name + ': ' + message )
           
    def shutdown( self, reason = None ):
        """
        Shut down the client for some reason.
        @param reason The reason why the client is being shut down. This will later raise an
        exception, so take care in using it.
        """
       
        if self.timer != None:
            self.timer.cancel()
            self.timer = None
        if self.server != None:
            self.server.shutdown()
            self.server = None
        self.reason = reason
        self.shutdownEvent.set()
       
    def nameConflict( self ):
        """
        There was some kind of conflict with the user's name.
        @deprecated This is handled inside the beginning of run() now.
        """
       
        verbose( "name conflict" )
        print "There was a conflict with your chosen name. Please choose another."
        self.shutdown( "Name Conflict" )
       
    def display( self, message ):
        """
        Display a message from peers, the server or client to the user in the message window.
        @param message The message to display.
        """
       
        self.messengerWindow.move( 0, 0 )
        self.messengerWindow.deleteln()
        height, width = self.messengerWindow.getmaxyx()
        self.messengerWindow.addstr( height - 1, 0, message.rstrip() )
        self.messengerWindow.redrawwin()
        self.messengerWindow.refresh()
        self.userWindow.refresh()
       
    class ClientError( Exception ):
        """
        Used to treat internal exceptions
        """
       
        def __init__( self, reason ):
            #Exception.__init__()
            self.reason = reason
       
   
if __name__ == '__main__':
    # Identify this computer's hostname and IP address.
    import socket
    global myName, myIP, myPort, args, clientList, ShutdownEvent, tik, time
    time = 0
    clientList = dict()
    myName = socket.gethostname()
    try:
        # this will throw an error if there is no domain controller for the network
        myIP = socket.gethostbyname( socket.getfqdn() )
    except socket.gaierror:
        # if there is no domain controller, use the known hostname to retrieve the IP address
        myIP = socket.gethostbyname( myName )
    else:
        # if we get here for some reason, use the loopback IP.
        myIP = '127.0.0.1'
    #print '"' + myName + '"', myIP


    # Generate a 64-bit salt
    #import random, base64, marshal
    #random.seed()
    #mySalt = base64.urlsafe_b64encode( marshal.dumps( random.randint( 0, 2**64-1 ) ) )
    #print mySalt
   
   
    # Load Python's argument parsing module to provide parsing and support
    from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, FileType
    parser = ArgumentParser( description = 'Network Programming - Project 01 - Peer-to-peer messaging',
        formatter_class = ArgumentDefaultsHelpFormatter )
   
   
    # should the script run as a server?
    if '-s' in sys.argv or '--server' in sys.argv:
        # run as server
        myName = 'Server'
    else:
        # provide the user with more command line information on usage
        parser.add_argument( 'host', help = 'Specify the hostname of the server to connect to.' )
   
    # add all the other command line arguments for parsing
    parser.add_argument(
        '-u', '--user',
        help = 'Specify a user or machine name.',
        default = myName )
    parser.add_argument(
        '-p', '--port',
        help = 'Specify the port number to use.',
        default = 1337,
        type = int )
    parser.add_argument(
        '-s', '--server',
        help = 'Run as a server.',
        action = 'store_true' )
    parser.add_argument(
        '-v', '--verbose',
        help = 'Display extra information.',
        action = 'store_true' )
    #parser.add_argument(
    #    '-t', '--test',
    #    help = 'Perform self tests.',
    #    action = 'store_true' )
    parser.add_argument(
        '-l', '--log',
        help = 'Perform self tests.',
        type = FileType('w'),
        default = sys.stdout )
    args = parser.parse_args()
   
   
    # configure the logging utility
    logging.basicConfig( stream = args.log, datefmt = '%Y-%m-%d %H:%M:%S',
        format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )
    logger = logging.getLogger()
    if args.verbose:
        logger.setLevel( logging.DEBUG )
    else:
        logger.setLevel( logging.WARNING )
   
   
    # configure the serialization allowances.
    cerealizer.register( NameConflict )
    cerealizer.register( PeerConnect )
    cerealizer.register( PeerDisconnect )
    cerealizer.register( PeerMessage )
    cerealizer.register( PeerBroadcast )
    cerealizer.register( ClientConnect )
    cerealizer.register( ClientMaintain )
    cerealizer.register( ClientDisconnect )
    cerealizer.register( Acknowledge )

   
    # check to see if the server is being initiated
    if args.server:
        myPort = args.port
        verbose( "Server initiating on port", myPort )
        server = ThreadedTCPServer( ('localhost', myPort), ServerHandler )
        myIP, myPort = server.server_address
        serverThread = threading.Thread( target = server.serve_forever, name = 'ServerThread' )
        serverThread.daemon = True
        serverThread.start()
        verbose( "Server thread started:", serverThread.name )
       
        if False: #args.test:
            c = ClientConnect()
            c.payload = Peer()
            c.payload.name = 'test'
            c.payload.ip = '127.0.0.1'
            c.payload.port = 31337
            Send( 'localhost', args.port, c )
        else:
            # perform a tick every 20 seconds
            t = threading.Timer( 20, Tick )
            tik = False
            t.start()
            verbose( "Timer started..." )
           
            # wait for a shutdown event
            ShutdownEvent = threading.Event()
            ShutdownEvent.wait()
            t.cancel()
       
        server.shutdown()
    else:
        global clientApp
        verbose( "client:", args.user, myIP, args.host, args.port )
        clientApp = ClientApp( args.user, myIP, args.host, args.port )
        clientApp.start()

    # shut down the logging interface
    logging.shutdown()
       

#eof