#! /usr/bin/env python

from threading import Thread, currentThread
from Pyro.errors import NamingError
import Pyro.core, Pyro.naming, Pyro.util
import sys

CHAT_SERVER_GROUP = ":ChatBox"
CHAT_SERVER_NAME = CHAT_SERVER_GROUP+".Server"


# Chat box administration server.
# Handles logins, logouts, channels and nicknames, and the chatting.
class ChatBox(Pyro.core.ObjBase):
	def __init__(self):
		Pyro.core.ObjBase.__init__(self)
		self.channels={}		# registered channels { channel --> (nick, client callback) list }
		self.nicks=[]			# all registered nicks on this server
		self.publishMutex = Pyro.util.getLockObject()
	def getChannels(self):
		return self.channels.keys()
	def getNicks(self):
		return self.nicks
	def join(self, channel, nick, callback):
		if nick in self.nicks:
			raise ValueError,'this nick is already in use'
		if not self.channels.has_key(channel):
			print 'CREATING NEW CHANNEL',channel
			self.channels[channel]=[]
		self.channels[channel].append((nick,callback))
		self.nicks.append(nick)
		callback._setOneway('message')	# don't wait for results for this method
		print nick,'JOINED',channel
		self.publish(channel,'SERVER','** '+nick+' joined **')
		nicks=[]
		for (n,c) in self.channels[channel]:
			nicks.append(n)
		return nicks
	def leave(self,channel,nick):
		if not self.channels.has_key(channel):
			print 'IGNORED UNKNOWN CHANNEL',channel
			return
		for (n,c) in self.channels[channel]:
			if n==nick:
				self.channels[channel].remove((n,c))
				break
		self.publish(channel,'SERVER','** '+nick+' left **')
		if len(self.channels[channel])<1:
			del self.channels[channel]
			print 'REMOVED CHANNEL',channel
		self.nicks.remove(nick)
		print nick,'LEFT',channel
	def publish(self, channel, nick, msg):
		# This must be performed in its own thread otherwise a thread deadlock may occur
		# when the publish is performed from within a call of a subscriber!
		Thread(target=self._do_publish, args=(channel, nick ,msg)).start()
	def _do_publish(self, channel, nick, msg):
		if not self.channels.has_key(channel):
			print 'IGNORED UNKNOWN CHANNEL',channel
			return
		for (n,c) in self.channels[channel][:]:		# use a copy of the list
			try:
				# we claim ownership of the proxy, but only in a thread lock
				# noone else is calling methods on the c, so this is safe to do.
				self.publishMutex.acquire()
				try:
					c._transferThread()
					c.message(nick,msg)	# oneway call
				finally:
					self.publishMutex.release()
			except Pyro.errors.ConnectionClosedError,x:
				# connection dropped, remove the listener if it's still there
				# check for existence because other thread may have killed it already
				if (n,c) in self.channels[channel]:
					self.channels[channel].remove((n,c))
					print 'Removed dead listener',n,c


def main():
	Pyro.core.initServer()
	daemon = Pyro.core.Daemon()
	ns = Pyro.naming.NameServerLocator().getNS()
	daemon.useNameServer(ns)

	# make sure our namespace group exists, and that our object name doesn't
	try:
		ns.createGroup(CHAT_SERVER_GROUP)
	except NamingError:
		pass
	try:
		ns.unregister(CHAT_SERVER_NAME)
	except NamingError:
		pass

	uri=daemon.connect(ChatBox(),CHAT_SERVER_NAME)

	# enter the service loop.
	print 'Chatbox open.'
	daemon.requestLoop()


if __name__=='__main__':
	main()

