#!/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: /cvsroot/fnorb/fnorb/orb/IIOPConnection.py,v $
# Version:      @(#)$RCSfile: IIOPConnection.py,v $ $Revision: 1.11 $
#
#############################################################################
""" IIOPConnection class. """


# Standard/built-in modules.
import socket, struct

# Try to import select.
# It will fail in Jython.
try:
    import select
    select_available = 1
except:
    select_available = 0

# Fnorb modules.
import CORBA, Connection, OctetStream, Util


class IIOPConnection(Connection.Connection):
    """ IIOPConnection class. """

    def __init__(self, _socket=None):
	""" Provide an IIOP connection to a remote object!

	'_socket' is a socket that is *already* connected.

	"""
	# If we are given a "here's one I prepared earlier" socket, then we
	# use that.
	#
	if _socket is not None:
	    self.__socket = _socket
	    self.__connected = 1

	    # The 'TCP_NODELAY' option disables the TCP Nagle algorithm which
	    # prevents the transmission of small packets (like for example
	    # 'CloseConnection' messages).
	    #
	    # fixme: We don't use symbolic constants from the 'socket' module
	    # as they don't appear to be present on all platforms (eg. Linux!).
	    # Is this just an ommision or is there a reason?!?
	    self.__socket.setsockopt(6, # IPPROTO_TCP
				     1, # TCP_NODELAY
				     1) # Option on!

	# Otherwise, we will create a socket when the 'connect' method is
	# called.
	else:
	    self.__connected = 0

        self.__negotiated_codeset = None

	return

    #########################################################################
    # Connection interface.
    #########################################################################

    def connect(self, address):
	""" Connect to the remote object. """

	# Create a socket and connect to the remote object.
	try:
	    self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	    self.__socket.connect(address)

	    # Set the socket by default to NON-blocking mode.
            # Jython sockets have no 'setblocking' method.
            if hasattr(self.__socket, 'setblocking'):
	        self.__socket.setblocking(0)

	    # Connected!
	    self.__connected = 1

	except socket.error:
	    raise CORBA.COMM_FAILURE() # System exception.

	return

    def disconnect(self):
	""" Disconnect from the remote server. """

	# Make sure that we are actually connected!
	if self.__connected:
	    try:
		# Close the connection.
		self.__socket.close()

	    except socket.error:
		pass

	    # Disconnected.
	    self.__connected = 0

	return

    def send(self, data, block=0):
	""" Send as much of the data as we can. """

	try:
	    n = self.__socket.send(data)

	except socket.error:
	    raise CORBA.COMM_FAILURE(0, CORBA.COMPLETED_MAYBE)

	return n

    def recv(self, n):
	""" Receive at most 'n' bytes of data. """
	
	try:
	    # Block until there is some data present on the socket.
	    #
	    # We do a 'select' instead of a blocking read, because 'select'
	    # can be interrupted by closing the socket. This is useful in
	    # the presence of threads when we need to shut down the read
	    # thread.
	    if select_available:
		try:
	            (iwtd, owtd, ewtd) = select.select([self.__socket], [], [])

	            # Read the data.
	            data = self.__socket.recv(n)

	        except select.error:
	            raise CORBA.COMM_FAILURE(0, CORBA.COMPLETED_MAYBE)

	    else:
	        # Read the data.
	        data = self.__socket.recv(n)

	# fixme: The 'AttributeError' is caught because it seems that on
	# Windows platforms this exception is raised when the socket has been
	# closed by the peer?
	except (socket.error, ValueError, AttributeError):
	    raise CORBA.COMM_FAILURE(0, CORBA.COMPLETED_MAYBE)
	
	return data

    def is_connected(self):
	""" Are we connected to the remote address? """

	return self.__connected

    def blocking(self, blocking):
	""" Set the connection to blocking (1) or non-blocking (0). """

        # Jython sockets have no 'setblocking' method.
        if hasattr(self.__socket, 'setblocking'):
	    self.__socket.setblocking(blocking)

	return

    def handle(self):
	""" Return my underlying I/O handle.

	In this case, my I/O handle is the file descriptor of my socket. 

	"""
	return self.__socket.fileno()

    def get_tcs_c(self):
        """Return the negotiated transmission code set for characters."""

        if self.__negotiated_codeset is None:
            return 0

        return self.__unpacked_codeset.char_data

    def get_tcs_w(self):
        """Return the negotiated transmission code set for wide characters."""

        if self.__negotiated_codeset is None:
            return 0

        return self.__unpacked_codeset.wchar_data

    def set_negotiated_codeset(self, codeset):
        """Set the flag that the codeset has been sent."""
        self.__negotiated_codeset = codeset
        csc_tc = CORBA.typecode(
            "IDL:omg.org/CONV_FRAME/CodeSetContext:1.0")
        encaps = OctetStream.Encapsulation(codeset.context_data)
        csc = csc_tc._fnorb_unmarshal_value(encaps.cursor())
        self.__unpacked_codeset = csc

        # Set-up the conversion functions. ASCII (0x00010020)
        # and Latin-1 (0x00010001) are treated as equivalent to
        # avoid conversions in the common case

        pcs = CORBA.ORB_init()._fnorb_get_process_codeset()
        if (csc.char_data == pcs or
            csc.char_data == 0x00010020 and pcs == 0x00010001 or
            csc.char_data == 0x00010001 and pcs == 0x00010020):
            self.encode_char = lambda x:x
            self.decode_char = lambda x:x
        else:
            try:
                del self.encode_char
                del self.decode_char
            except AttributeError:
                pass
            self.__pcs = Util.osf2codec[pcs]
            self.__tcs_c = Util.osf2codec[csc.char_data]

        self.__wcs = Util.osf2codec[csc.wchar_data]

    def get_negotiated_codeset(self):
        return self.__negotiated_codeset

    def encode_char(self, data):
        try:
            return unicode(data, self.__pcs).encode(self.__tcs_c)
        except UnicodeError:
            raise CORBA.DATA_CONVERSION

    def decode_char(self, codeset):
        try:
            return unicode(data, self.__tcs_c).encode(self.__pcs)
        except UnicdeError:
            raise CORBA.DATA_CONVERSION

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