#!/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/SelectReactor.py,v $
# Version:      @(#)$RCSfile: SelectReactor.py,v $ $Revision: 1.18 $
#
#############################################################################
""" A simple 'select' Reactor. """


# Standard/built-in modules.
import select

# Fnorb modules.
import Reactor


def SelectReactor_init():
    """ Initialise the SelectReactor.

    There can only be one instance of any concrete reactor class per process.

    """
    try:
	reactor = SelectReactor()

    except Reactor.Reactor, reactor:
	pass

    return reactor


class SelectReactor(Reactor.Reactor):
    """ The SelectReactor.

    This reactor uses a standard 'select' event loop.

    """
    def __init__(self):
	""" Constructor. """

	# There can be at most one instance of any concrete reactor class per
	# process
	if Reactor.Reactor._instance is not None:
	    # A reactor already exists.
	    raise Reactor.Reactor._instance

	Reactor.Reactor._instance = self

	# A dictionary of all registered event handlers. All handles in the
	# SelectReactor are actually just file descriptors.
	self.__handlers = {} # {Handle: (EventHandler, Mask)}

	return

    #########################################################################
    # Reactor interface.
    #########################################################################

    def register_handler(self, handler, mask):
	""" Register an event handler. """

	# Get the event handler's underlying I/O handle.
	handle = handler.handle()

	# If the handler is already registered then update the mask.
	try:
	    # Find the event handler associated with the handle.
	    (handler, handler_mask) = self.__handlers[handle]

	    # Update the mask.
	    handler_mask = handler_mask | mask

	# Otherwise, add the new handler.
	except KeyError:
	    handler_mask = mask

	# Do the add/update.
	self.__handlers[handle] = (handler, handler_mask)

	return

    def unregister_handler(self, handler, mask):
	""" Withdraw a handler's registration. """

	# Get the event handler's underlying I/O handle.
	handle = handler.handle()

	try:
	    # Find the event handler associated with the handle.
	    (handler, handler_mask) = self.__handlers[handle]

	    # Update the mask.
	    handler_mask = handler_mask & ~mask

	    # If the mask is now zero (ie. the handler is no longer interested
	    # in *any* events), then delete the handler.
	    if handler_mask == 0:
		del self.__handlers[handle]

	    # Otherwise, just update the handler's mask.
	    else:
		self.__handlers[handle] = (handler, handler_mask)

	# Ignore any attempts to un-register a handler that wasn't registerd!
	except KeyError:
	    pass

	return

    def start_event_loop(self, timeout=None):
	""" Start the event loop. """

	self.__is_alive = 1
	while self.__is_alive:
	    # Wait for and process a single event.
	    self.do_one_event(timeout)

	# Close all registered handlers.
	self.__close_all_handlers()
	    
	return

    def stop_event_loop(self):
	""" Stop the event loop. """

	# This variable is checked before each call to 'do_one_event'.
	self.__is_alive = 0

	return

    def do_one_event(self, timeout=None):
	""" Dispatch a single event. """

	# Get the read/write/exception handles for all registered handlers.
	(iwtd, owtd, ewtd) = self.__get_handles()

	# Blocking select (with timeout if specified).
	if timeout is None:
	    (iwtd, owtd, ewtd) = select.select(iwtd, owtd, ewtd)
	    
	else:
	    (iwtd, owtd, ewtd) = select.select(iwtd, owtd, ewtd, timeout)

	# Set up a 'try' block to catch 'KeyError' exceptions (sometimes
	# handlers may remove themselves before we get to process them e.g
	# if a handler has both a read and a write event then the processing
	# of the read event could call 'unregister_handler' before the
	# write event is processed).
	try:
	    # Read events.
	    if len(iwtd) > 0:
		for handle in iwtd:
		    # Find the event handler associated with the handle.
		    (handler, mask) = self.__handlers[handle]
		    
		    # Handler callback.
		    handler.handle_event(Reactor.READ)

	    # Write events.
	    if len(owtd) > 0:
		for handle in owtd:
		    # Find the event handler associated with the handle.
		    (handler, mask) = self.__handlers[handle]

		    # Handler callback.
		    handler.handle_event(Reactor.WRITE)

	    # Exceptional events.
	    if len(ewtd) > 0:
		for handle in ewtd:
		    # Find the event handler associated with the handle.
		    (handler, mask) = self.__handlers[handle]

		    # Handler callback.
		    handler.handle_event(Reactor.EXCEPTION)

	except KeyError:
	    pass

	return

    # The following two methods are provided to allow Fnorb events to be
    # handled in other event loops.
    def handles(self):
	""" Return the read/write/exception handles for registered handlers.

	The return value is a tuple in the form:-

	([ReadHandles], [WriteHandles], [ExceptionHandles])

	"""
	return self.__get_handles()

    def handle_one_event(self, handle, mask):
	""" Handle a single event. """

	# Read event.
	if mask & Reactor.READ:
	    # Find the event handler associated with the handle.
	    (handler, handler_mask) = self.__handlers[handle]
	    
	    # Handler callback.
	    handler.handle_event(Reactor.READ)

	# Write event.
	if mask & Reactor.WRITE:
	    # Find the event handler associated with the handle.
	    (handler, handler_mask) = self.__handlers[handle]

	    # Handler callback.
	    handler.handle_event(Reactor.WRITE)

	# Exception event.
	if mask & Reactor.EXCEPTION:
	    # Find the event handler associated with the handle.
	    (handler, handler_mask) = self.__handlers[handle]

	    # Handler callback.
	    handler.handle_event(Reactor.EXCEPTION)

	return

    #########################################################################
    # Private interface.
    #########################################################################

    def __get_handles(self):
	""" Return the read/write/exception handles for registered handlers.

	The return value is a tuple in the form:-

	([ReadHandles], [WriteHandles], [ExceptionHandles])

	"""
	iwtd = {}
	owtd = {}
	ewtd = {}

	for handle in self.__handlers.keys():
	    # Find the event handler associated with the handle.
	    (handler, mask) = self.__handlers[handle]

	    if mask & Reactor.READ:
		iwtd[handle] = None

	    if mask & Reactor.WRITE:
		owtd[handle] = None

	    # Listen for errors on all handles.
	    ewtd[handle] = None

	return (iwtd.keys(), owtd.keys(), ewtd.keys())

    def __close_all_handlers(self):
	""" Close all registered handlers. """

	# We work on a copy of the list, 'cos the handlers will most likely
	# call 'unregister_handler' in their 'close' method which can cause
	# them to be deleted from the handler dictionary, 'self.__handlers'.
	for (handler, mask) in self.__handlers.values():
	    # Handler callback.
	    handler.handle_close()

	return

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