#############################################################################
#
#	Pyro Naming Service
#
#	This is part of "Pyro" - Python Remote Objects
#	which is (c)1999 Irmen de Jong - irmen@bigfoot.com.
#
#############################################################################


# incoming requests are pickled tuples, of the type:
#	(action, name, URI)
# where action is one of req_Register, req_Resolve,
# name is the name of the object to find
# URI is the URI of the object (only with register)
# req_Resolve returns a PythonURI object. None means not found.


import sys, time
import socket
import SocketServer
from Pyro.core import *

try:
	import cPickle; pickle = cPickle
except ImportError:
	import pickle


PYRONS_VERSION = '0.6'

class NamingError(PyroError):   pass


#############################################################################
#
# The Pyro NameServer Locator.
# Use a broadcast mechanism to find the broadcast server of the NS which
# can provide us with the URI of the NS.
#
#############################################################################

class NameServerLocator:

	def sendSysCommand(self,parameter,host=None,port=None,trace=0):
		if not port:
			# select the default broadcast port
			port = Pyro.config.PYRO_NS_BC_PORT
		# We must discover the location of the naming service.
		# Pyro's NS can answer to broadcast requests.
		s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		s.bind(('', 0))
		if not host:
			# use a broadcast
			destination = ('<broadcast>', port)
			s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
		else:
			# use unicast
			destination = (host, port)
			
		if trace:
			print 'LOCATOR: Searching Pyro Naming Service...'
		for i in range(Pyro.config.PYRO_BC_RETRIES+1):
			rq = pickle.dumps((parameter,None))
			s.sendto(rq, destination)		# send to Pyro NS's bc port
			ins,outs,exs = select.select([s],[],[],Pyro.config.PYRO_BC_TIMEOUT)
			if s in ins:
				reply, fromaddr = s.recvfrom(1000)
				return reply
			if trace and i<Pyro.config.PYRO_BC_RETRIES:
				print 'LOCATOR: Retry',i+1
		raise PyroError('Naming Service not found')
	
	def getNS(self,host=None,port=None,trace=0):
		reply = self.sendSysCommand('location',host,port,trace)
		return NameServerProxy(Pyro.core.PyroURI(reply))


###### This class is generated by the PyroC proxy compiler ######
###### Everytime the NameServer class changes, regenerate! ######

class NameServerProxy:

	# this class 

	def __init__(self,URI):
		self.URI = URI
		self.objectID = URI.objectID
		self.adapter = Pyro.core.getProtocolAdapter(self.URI.protocol)
		self.adapter.bindToURI(URI)
	def ping(self):
		return self.adapter.remoteInvocation('ping',0)
	def register(self,name,URI):
		return self.adapter.remoteInvocation('register',0,name,URI)
	def resolve(self,name):
		return self.adapter.remoteInvocation('resolve',0,name)
	def status(self):
		return self.adapter.remoteInvocation('status',0)
	def unregister(self,name):
		return self.adapter.remoteInvocation('unregister',0,name)		

###### END of generated code ######

#############################################################################
#
#	The Name Server (a Pyro Object).
#
#############################################################################

class NameServer(Pyro.core.ObjBase):

	def __init__(self):
		Pyro.core.ObjBase.__init__(self)
		self.database={}
	def register(self,name,URI):
		if self.database.has_key(name):
			raise PyroException(NamingError('entry already exists'))
		self.database[name]=URI
		print '*** registered \''+name+'\' with URI '+str(URI)
	def unregister(self,name):
		try:
			del self.database[name]
			print '*** unregistered \''+name+'\''
		except KeyError:
			pass
	def resolve(self,name):
		try:
			return self.database[name]
		except KeyError:
			raise PyroException(NamingError('entry not found'))
	def status(self):
		# return the contents of our database
		return self.database
	def ping(self):
		pass  # just accept a remote invocation.
			

#############################################################################
#
#	The Persistent Name Server (a Pyro Object).
#	NOTE: This implementation is LIKELY TO CHANGE because it's EXPERIMENTAL.
#
#############################################################################

class PersistentNameServer(NameServer):

	def __init__(self,dbfile=None,name=Pyro.config.PYRO_NS_NAME):
		NameServer.__init__(self)
		# change to a persistent naming service
		import anydbm,os,marshal
		self.dbfile = os.path.join(Pyro.config.PYRO_STORAGE,dbfile or 'NS.database')
		self.database = anydbm.open(self.dbfile,'c')
		self.nsname = name
		try:
			# Try to figure our persistent UUID by looking in the database
			uri=Pyro.core.PyroURI(self.database[self.nsname])
			self.setPersistentUUID(uri.objectID)
		except KeyError:
			pass  # Keep the default UUID
		if hasattr(self.database,'sync'):
			self.dbsync = self.database.sync
		else:
			self.dbsync = lambda:None
		self.dbsync()
	def checkForInitialCreate(self):
		# Check if the name is already in the DB. If it isn't, this is
		# the first time the DB is used... (probably just created)
		try:
			uri = self.database[self.nsname]
			return 0
		except KeyError:
			return 1
	def register(self,name,URI):
		NameServer.register(self,name,str(URI))
		self.dbsync()
	def unregister(self,name):
		NameServer.unregister(self,name)
		self.dbsync()
	def resolve(self,name):
		try:
			return Pyro.core.PyroURI(self.database[name])
		except KeyError:
			raise PyroException(NamingError('entry not found'))
	def status(self):
		# return the contents of our database
		db = {}
		for k in self.database.keys():
			db[k]=self.database[k]
		return db


#############################################################################
#
# The broadcast server which listens to broadcast requests of clients who
# want to discover our location.
# These clients get back a pickled tuple (NS's IP, NS's port).
#
#############################################################################


class BroadcastServer(SocketServer.UDPServer):

	"The Pyro Name Server's broadcast server. It is used to make it easy for the clients to discover the name server itself (by means of a broadcast mechanism). The bcRequestHandler class handles the requests."

	
	nameServerURI = ''	# the Pyro URI of the Name Server

	def server_activate(self):
		self.shutdown=0				# should the server loop stop?
		self.preferredTimeOut=3.0	# preferred timeout for the server loop
			
	def setNS_URI(self,URI):
		self.nameServerURI=URI

	def bcCallback(self,ins):
		for i in ins:
			i.handle_request()
	
		
class bcRequestHandler(SocketServer.BaseRequestHandler):

	"See BroadcastServer."
	
	def handle(self):
		print '*** incoming broadcast request from',self.client_address[0]
		try:
			# request is tuple: (data, client socket)
			(cmd, param) = pickle.loads(self.request[0])
			if cmd=='location':
				# somebody wants to know our location
				self.request[1].sendto(self.server.nameServerURI,self.client_address)
			elif cmd=='shutdown':
				# we should die!?
				print 'Shutdown received.'
				self.request[1].sendto('Will shut down shortly, when no more requests arrive',self.client_address)
				self.server.shutdown=1
			else:
				print 'Invalid command ignored:',cmd
		except pickle.UnpicklingError:
			print 'Invalid request ignored'



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

def startServer(nsport=Pyro.config.PYRO_NS_PORT,bcport=Pyro.config.PYRO_NS_BC_PORT,
                persistent=0, dbfile=None):

	Pyro.core.initServer()
	PyroDaemon = Pyro.core.Daemon(port=nsport)
	if persistent:
		ns=PersistentNameServer(dbfile,Pyro.config.PYRO_NS_NAME)
		PyroDaemon.useNameServer(ns)
		if ns.checkForInitialCreate():
			# NS database has just been created,
			# should enter the NS itself in the database
			PyroDaemon.connect(ns,Pyro.config.PYRO_NS_NAME)
		else:
			# NS is already in the database
			PyroDaemon.connectPersistent(ns,Pyro.config.PYRO_NS_NAME)
	
	else:
		ns=NameServer()
		PyroDaemon.useNameServer(ns)
		PyroDaemon.connect(ns,Pyro.config.PYRO_NS_NAME)
	
	bcserver = BroadcastServer(('',bcport),bcRequestHandler)
	bcserver.setNS_URI(str(ns.resolve(Pyro.config.PYRO_NS_NAME)))
	print 'Pyro Naming Service V'+PYRONS_VERSION+'.'
	if persistent:
		print 'Persistent mode, database is in',ns.dbfile
	print 'Running on',PyroDaemon.hostname,'port',PyroDaemon.port,PyroDaemon.socket.getsockname()
	print 'Broadcast daemon on port',bcport
	print 'Ready.'
	
	while not bcserver.shutdown:
		# I use a timeout here because else you can't break gracefully on WinNT
		PyroDaemon.handleRequests(bcserver.preferredTimeOut,[bcserver],bcserver.bcCallback)
	
	print 'Server shut down gracefully.'

def main():
	if __name__!='__main__':
		del sys.argv[0]	# remove '-c' arg
	import Pyro.util
	Args = Pyro.util.ArgVParser()
	Args.parse(sys.argv)
	try:
		Args.getArg('-h')
		print 'Usage: ns [-p port] [-bp port] [-db [databasefile]]'
		print '  where -p  = NS server port'
		print '        -bp = NS broadcast port'
		print '        -db = use persistent database, provide optional filename'
		raise SystemExit
	except KeyError:
		pass
	try:
		port = int(Args.getArg('-p'))
	except KeyError:
		port = Pyro.config.PYRO_NS_PORT
	try:
		bcport = int(Args.getArg('-bp'))
	except KeyError:
		bcport = Pyro.config.PYRO_NS_BC_PORT
	try:
		dbfile = Args.getArg('-db')
		persistent = 1
	except KeyError:
		persistent = 0
		dbfile = None
	
	Args.printIgnored()
	startServer(port,bcport,persistent,dbfile)

