#!/usr/bin/env python
#############################################################################
# Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1997, 1998, 1999
# All Rights Reserved.
#
# The software contained on this media is the property of the DSTC Pty
# Ltd.  Use of this software is strictly in accordance with the
# license agreement in the accompanying LICENSE.HTML file.  If your
# distribution of this software does not contain a LICENSE.HTML file
# then you have no rights to use this software in any manner and
# should contact DSTC at the address below to determine an appropriate
# licensing arrangement.
# 
#      DSTC Pty Ltd
#      Level 7, GP South
#      Staff House Road
#      University of Queensland
#      St Lucia, 4072
#      Australia
#      Tel: +61 7 3365 4310
#      Fax: +61 7 3365 4311
#      Email: enquiries@dstc.edu.au
# 
# This software is being provided "AS IS" without warranty of any
# kind.  In no event shall DSTC Pty Ltd be liable for damage of any
# kind arising out of or in connection with the use or performance of
# this software.
#
# Project:      Fnorb
# File:         $Source: /projects/fnorb/Fnorb-1.1/orb/RCS/GIOPConnectionHandler.py,v $
# Version:      @(#)$RCSfile: GIOPConnectionHandler.py,v $ $Revision: 1.5 $
#
#############################################################################
""" GIOPConnectionHandler class. """


# Standard/built-in modules.
import string

# Fnorb modules.
import CORBA, OctetStream


class GIOPConnectionHandlerListener:
    """ Listener interface for the GIOPConnectionHandler class. """

    def message_received(self, reply_header, cursor):
	""" Called when a complete GIOP message has been received. """

	pass

    def message_sent(self):
	""" Called when a complete GIOP message has been sent. """
	
	pass


class GIOPConnectionHandler:
    """  GIOPConnectionHandler class. """
    
    # Maximum size of the buffer used for reading from connections (we
    # deliberately do *not* make this figure a power of two to make sure that
    # memory allocations will be as efficient as possible).
    BUFSIZE = 8000

    def __init__(self, listener, connection):
	""" Provide an interface to a remote object!

	'listener'   is the listener that is using this handler.
	'connection' is the connection to handle!

	"""
	self.__listener = listener
	self.__connection = connection

	# Details of the current incoming GIOP message.
	self.__incoming = []
	self.__incoming_size = 0
	self.__message_size = 0

	# Details of the current outgoing GIOP message.
	self.__outgoing = None
	self.__outgoing_size = 0

	return

    def pseudo__del__(self):
	""" Pseudo destructor to remove circular references. """

	# Clean up the reference to the listener.
	del self.__listener

	return

    #########################################################################
    # GIOPConnectionHandler interface.
    #########################################################################

    def send(self, message):
	""" Perform a single 'send' operation on the connection. """

	if self.__outgoing is None:
	    # Extract the string of bytes from the message.
	    self.__outgoing = message

	# Extract the string of bytes from the message.
	data = self.__outgoing.data()

	# Starting a new message.
	if self.__outgoing_size == 0:
	    n = self.__connection.send(data)
	    self.__outgoing_size = self.__outgoing_size + n

	# Continuing transmission of a previous message.
    	elif self.__outgoing_size > 0 and self.__outgoing_size < len(data):
	    n = self.__connection.send(data[self.__outgoing_size:])
	    self.__outgoing_size = self.__outgoing_size + n

	# If the entire message has been sent.
	if self.__outgoing_size > 0 and self.__outgoing_size == len(data):
	    # Get ready to start the next message.
	    self.__outgoing = None
	    self.__outgoing_size = 0

	    # Message sent!
	    self.__listener.message_sent()

	return n

    def recv(self):
	""" Perform a single 'recv' operation on the connection. """
	
	# Read the GIOP message header.
	if self.__incoming_size < 12:
	    # Get the next chunk of the GIOP header.
	    chunk = self.__connection.recv(12 - self.__incoming_size)
	    # Zero length reads not allowed!
	    if len(chunk) == 0:
		raise CORBA.COMM_FAILURE(0, CORBA.COMPLETED_MAYBE)

	    # Add the chunk to the incoming message.
	    self.__incoming_size = self.__incoming_size + len(chunk)
	    self.__incoming.append(chunk)

	    # Have we got the entire header now?
	    if self.__incoming_size == 12:
		# Join the chunks together.
		data = string.join(self.__incoming, '')

		# Unmarshal the header.
		octet_stream = OctetStream.GIOPMessage(data)

		# Get the size of the message body.
		self.__message_size = octet_stream.header().message_size

	# Read the message body.
	elif self.__incoming_size >= 12 and \
	     self.__incoming_size < self.__message_size + 12:

	    # Work out the number of bytes remaining.
	    remaining = (self.__message_size + 12) - self.__incoming_size

	    # Calculate the size of the buffer.
	    bufsize = min(remaining, GIOPConnectionHandler.BUFSIZE)

	    # Get the next chunk of the message.
	    chunk = self.__connection.recv(bufsize)
	    if len(chunk) == 0:
		raise CORBA.COMM_FAILURE(0, CORBA.COMPLETED_MAYBE)
		    
	    # Add the chunk to the incoming message.
	    self.__incoming_size = self.__incoming_size + len(chunk)
	    self.__incoming.append(chunk)

	# Has the entire message been read?
	if self.__incoming_size >= 12 and \
	   self.__incoming_size == self.__message_size + 12:

	    # Join the chunks together.
	    data = string.join(self.__incoming, '')

	    # Reset ready for the next message.
	    self.__incoming = []
	    self.__incoming_size = 0
	    self.__message_size = 0

	    # Message received!
	    self.__listener.message_received(OctetStream.GIOPMessage(data))

	return

#############################################################################
