#############################################################################
#  
#	$Id: remote.py,v 1.2 2003/03/05 20:53:18 irmen Exp $
#	simple Pyro connection module, originally written by John Wiegley
#
#	This is part of "Pyro" - Python Remote Objects
#	which is (c) Irmen de Jong - irmen@users.sourceforge.net
#
#############################################################################

import signal
import string
import sys
import time
import types

import Pyro.errors
import Pyro.naming
import Pyro.core
import Pyro.util

from Pyro.protocol import ProtocolError 

true, false = 1, 0

verbose            = false
pyro_nameserver    = None
pyro_daemon        = None
client_initialized = false
server_initialized = false
daemon_host        = ''
daemon_port        = 0
daemon_objects     = []

def unregister_objects():
	if pyro_daemon is not None:
		global daemon_objects
		for obj in daemon_objects:
			try: pyro_daemon.disconnect(obj)
			except: pass
		daemon_objects = []

sys.exitfunc = unregister_objects

def find_nameserver(hostname = None, portnum = None):
	if hostname and string.find(hostname, '://') > 0:
		URI = Pyro.core.PyroURI(hostname)
		ns  = Pyro.naming.NameServerProxy(URI)
	else:
		if verbose:
			hostname=hostname or Pyro.config.PYRO_NS_HOSTNAME
			print 'Searching for Naming Service on %s:%d...' % \
				(hostname, portnum or Pyro.config.PYRO_NS_BC_PORT)
		locator = Pyro.naming.NameServerLocator()
		ns = locator.getNS(host = hostname, port = portnum)

	if verbose: print 'Naming Service found at', ns.URI

	return ns

class Error(Exception): pass

class Nameserver:
	"""This helper class allows the server to use Pyro's naming
	service for publishing certain objects by name.  It integrates
	better with remote.py, than Pyro.naming.NameServer does."""
	def __init__(self, ns, ns_port):
		self.ns      = ns
		self.ns_port = ns_port

	def __cmp__(self, other):
		return self.ns == other.ns and self.ns_port == other.ns_port

	def __str__(self):
		if self.ns_port:
			return "%s:%s" % (self.ns, self.ns_port)
		return self.ns

	def resolve(self, name, use_server_proxy = false):
		return get_remote_object(name, self.ns, self.ns_port)

	def register(self, name, object):
		return provide_local_object(object, name, self.ns, self.ns_port)

	def unregister(self, object):
		for obj in daemon_objects[:]:
			if obj.delegate is object:
				pyro_daemon.disconnect(obj)
				daemon_objects.remove(obj)

class Reference:
	def __init__(self, nameserver, id, object = None):
		"""Id is the moniker for the object.  Server is either an
		object, or a tuple of (nameserver, ns-port)"""
		self.__dict__['nameserver'] = nameserver
		self.__dict__['id']         = id
		self.__dict__['_v_o']       = None
		self.__dict__['_v_hash']    = None
		if object:
			self.__dict__['proxy']  = provide_local_object(object)

	def __nonzero__(self):
		return true

	def __str__(self):
		return "%(nameserver)s:%(id)s" % self.__dict__
		
	def __repr__(self):
		return "Reference(%(nameserver)s, %(id)s)" % self.__dict__

	def __cmp__(self, other):
		if self is other or \
			(type(other) is types.InstanceType and
			self.__class__ is other.__class__ and
			self.__dict__['nameserver'] == other.__dict__['nameserver'] and
			self.__dict__['id'] == other.__dict__['id']):
			return 0
		return -1

	def __hash__(self):
		h = self.__dict__['_v_hash']
		if h: return h
		h = self.__dict__['_v_hash'] = self.hash_str() 
		return h

	def hash_str(self):
		return hash("%(nameserver)s:%(id)s" % self.__dict__)

	def resolve(self):
		if self.__dict__.has_key('proxy'):
			proxy = self.__dict__['proxy']
			del self.__dict__['proxy']
			return proxy
		return self.__dict__['nameserver'].resolve(self.__dict__['id'])

	def bind(self, prop):
		if prop in dir(Reference):
			return
		if references.has_key(self):
			self.__dict__['_v_o'] = references[self]
		else:
			references[self] = self.__dict__['_v_o'] = self.resolve()

	def __hasattr__(self, prop):
		if prop in ('__getinitargs__', '__coerce__'):
			return None
		if self.__dict__['_v_o'] is None: self.bind(prop)
		return hasattr(self.__dict__['_v_o'], prop)

	def __getattr__(self, prop):
		if prop in ('__getinitargs__', '__coerce__'):
			raise AttributeError()
		if self.__dict__['_v_o'] is None: self.bind(prop)
		return getattr(self.__dict__['_v_o'], prop)

	def __delattr__(self, prop):
		if self.__dict__['_v_o'] is None: self.bind(prop)
		return delattr(self.__dict__['_v_o'], prop)

	def __setattr__(self, prop, value):
		if self.__dict__['_v_o'] is None: self.bind(prop)
		setattr(self.__dict__['_v_o'], prop, value)

	def __getstate__(self):
		temp = {}
		for key in self.__dict__.keys():
			if key != '_v_o' and key != '_v_hash':
				temp[key] = self.__dict__[key]
		return temp

	def __setstate__(self, value):
		for key in value.keys():
			self.__dict__[key] = value[key]
		self.__dict__['_v_o']    = None
		self.__dict__['_v_hash'] = None

def get_remote_object(name, hostname = None, portnum = None):
	global client_initialized, pyro_nameserver

	# initialize Pyro -- Python Remote Objects
	if not client_initialized:
		Pyro.core.initClient(verbose)
		client_initialized = true

	if pyro_nameserver is None or hostname:
		pyro_nameserver = find_nameserver(hostname, portnum)

	if verbose:
		print 'Binding object %s' % name

	try:
		URI = pyro_nameserver.resolve(name)
		if verbose:
			print 'URI:', URI

		return Pyro.core.DynamicProxyWithAttrs(URI)

	except Pyro.core.PyroError, x:
		raise Error("Couldn't bind object, nameserver says:", x)

def provide_local_object(object, name = None, hostname = None, portnum = None):
	global server_initialized, pyro_daemon, pyro_nameserver

	proxy_class = Pyro.core.DynamicProxyWithAttrs

	if not server_initialized:
		Pyro.core.initServer(verbose)
		server_initialized = true

	if pyro_daemon is None:
		pyro_daemon = Pyro.core.Daemon(host = daemon_host, port = daemon_port)

	# If no 'name' was specified, don't even bother with the
	# nameserver.
	if name:
		if pyro_nameserver is None or hostname:
			pyro_nameserver = find_nameserver(hostname, portnum)

		pyro_daemon.useNameServer(pyro_nameserver)

		if verbose:
			print 'Remoting object', name

			# tell nameserver to forget any earlier use of this name
		try:
			if pyro_nameserver.resolve(name):
				pyro_nameserver.unregister(name)
		except Pyro.errors.NamingError:
			pass

	if not isinstance(object, Pyro.core.ObjBase):
		slave = Pyro.core.ObjBase()
		slave.delegateTo(object)
		object = slave

	URI = pyro_daemon.connect(object, name)
	daemon_objects.append(object)

	proxy = proxy_class(URI)

	return proxy

abort = false

def interrupt(*args):
	global abort
	abort = true

if hasattr(signal,'SIGINT'): signal.signal(signal.SIGINT, interrupt)
if hasattr(signal,'SIGHUP'): signal.signal(signal.SIGHUP, interrupt)
if hasattr(signal,'SIGQUIT'): signal.signal(signal.SIGQUIT, interrupt)

def handle_requests(wait_time = None, callback = None):
	global abort

	abort = false

	if pyro_daemon is None:
		raise Error("There is no daemon with which to handle requests")
		return

	if wait_time:
		start = time.time()

	while not abort:
		try:
			pyro_daemon.handleRequests(wait_time)
			if wait_time:
				now = time.time()
				if callback and now - start > wait_time:
					callback()
					start = now
			elif callback:
				callback()

		except Exception, msg:
			if verbose:
				print "Error:", sys.exc_type, msg
			abort = true
		except:
			abort = true

	return abort

def unregister_object(object):
	if pyro_daemon is not None:
		try: pyro_daemon.disconnect(object)
		except: pass
		global daemon_objects
		if object in daemon_objects:
			daemon_objects.remove(object)

