# ex:ts=4
#
# Coolwater XMLRPC -- SourceLight-Medusa Powered XMLRPC Framework
#
# $LinuxKorea: xmlrpc.py,v 2.14 2001/10/29 07:34:46 perky Exp $
#
# Copyright 2001 Hye-Shik Chang. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name of author nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE. 
#
# code by Hye-Shik Chang <perky@linuxkorea.co.kr>
#

import socket
import string
import asyncore
import cw_medusa

try:
	from cStringIO import StringIO
except:
	from StringIO import StringIO

# SourceLight's py-xmlrpc *FAST* Implementation
import _xmlrpc

STATE_HEADER  = 0
STATE_CONTENT = 1

HEADER_CLEN   = 'content-length: '
HTTP_TERMINATOR = '\r\n'

class xmlrpc_root:
	pass

class xmlrpc_fancyroot(xmlrpc_root):
	
	def _getmethods(self, clsinst=None):
		if not clsinst:
			clsinst = self
		r = []
		try:
			oclass = clsinst.__class__.__dict__.keys()
		except AttributeError:
			oclass = []
		for m in oclass + dir(clsinst):
			if m[0] != '_' and type(getattr(clsinst, m)) in (type(self._getmethods), type(string.join)):
				r.append(m)
		return r
	
	def _getclasses(self, clsinst=None):
		if not clsinst:
			clsinst = self
		r = []
		try:
			oclass = clsinst.__class__.__dict__.keys()
		except AttributeError:
			oclass = []
		for m in oclass + dir(clsinst):
			if m[0] != '_' and type(getattr(clsinst, m)) in (type(self), type(string)):
				r.append(m)
		return r

	def _getallmethods(self, clsinst=None):
		if not clsinst:
			clsinst = self
		r = self._getmethods(clsinst)
		for cl in self._getclasses(clsinst):
			try:
				for m in self._getallmethods(getattr(clsinst, cl)):
					r.append(cl+'.'+m)
			except AttributeError:
				pass
		return r
	
	def _getdoc(self, name='', clsinst=None):
		if not clsinst:
			clsinst = self
		m = string.split(name, '.', 1)
		if len(m) > 1:
			return self._getdoc(m[1], getattr(clsinst, m[0]))
		elif m[0]:
			return self._getdoc('', getattr(clsinst, m[0]))
		else:
			try:
				return getattr(clsinst, '__doc__')
			except:
				return None


class xmlrpc_channel(cw_medusa.async_chat):

	def __init__(self, root, conn, addr, map=None):
		cw_medusa.async_chat.__init__(self, conn, map)
		self.root = root
		self.addr = addr
		self.buffer = StringIO()
		self.reqbuffer = StringIO() # XXX: Hmm. something leaking efficiency.
		self.setmode(STATE_HEADER)
		
	def log(*ignore):
		pass

	def collect_incoming_data(self, data):
		self.buffer.write(data)
	
	def setmode(self, mode):
		if mode == STATE_HEADER:
			self.state = STATE_HEADER
			self.set_terminator(HTTP_TERMINATOR)
			self.content_length = None
			self.reqbuffer.seek(0)
			self.reqbuffer.truncate()
		elif mode == STATE_CONTENT:
			self.state = STATE_CONTENT
			if self.content_length:
				self.set_terminator(self.content_length)
			else:
				print "no Content-length"

	def found_terminator(self):
		data = self.buffer.getvalue()
		self.buffer.seek(0)
		self.buffer.truncate()
		self.reqbuffer.write(data + HTTP_TERMINATOR)
		
		if self.state is STATE_HEADER:
			if string.lower(data[:len(HEADER_CLEN)]) == HEADER_CLEN:
				self.content_length = int(data[len(HEADER_CLEN):])
			elif not data: # header ends
				self.setmode(STATE_CONTENT)
		elif self.state is STATE_CONTENT:
			#print self.reqbuffer.getvalue()
			reqdata = _xmlrpc.parseRequest(self.reqbuffer.getvalue())

			o = self.root
			e = None
			try:
				for p in string.split(reqdata[0], '.'):
					o = getattr(o, p)
				result = apply(o, reqdata[1])
			except:
				_TOREPLY_ = _xmlrpc.buildFault(-1, repr(asyncore.compact_traceback()), {})
			else:
				_TOREPLY_ = _xmlrpc.buildResponse(result, {})
			self.push(_TOREPLY_)
			#print _TOREPLY_
			self.close_when_done()
			#self.setmode(STATE_HEADER)

class xmlrpc_server(asyncore.dispatcher):
	def __init__(self, root, address = ('', 43434)):
		self.root			= root
	
		if type(address) is type(()):
			family = socket.AF_INET
		else:
			family = socket.AF_UNIX
		
		self.create_socket(family, socket.SOCK_STREAM)
		self.address = address
		self.set_reuse_addr()
		self.bind(address)
		self.listen(128)
		self.listening = 1
	
	def handle_accept(self):
		if self.listening:
			conn, addr = self.accept()
			xmlrpc_channel(self.root, conn, addr)
	
	def writable(self):
		return 0

try:
	import forkfarm
except ImportError:
	pass
else:
	class xmlrpc_carrot(forkfarm.forkfarm_carrot):
		def run(self):
			mymap = {}
			xmlrpc_channel(self.side['root'], self.sock, self.addr, mymap)
			asyncore.loop(map=mymap)
	
	def xmlrpc_forkfarm(root, address=('', 43434), maxprocess=4, runlimit=0,
							farmer=None, carrot=None):
		""" Simple wrapper for creating xmlrpc server in ForkFarm model """
		side = {"root": root}
		if not carrot:	carrot = xmlrpc_carrot
		if not farmer:	farmer = forkfarm.forkfarm
		return farmer( address, carrot, maxprocess, runlimit=runlimit, side=side )

if __name__ == "__main__":

	class secdepth:
		def helloabroad(self):
			return 'merry christmas hohoho'

	class myroot(xmlrpc_fancyroot):
		import re
		secdepth = secdepth()
		
		def hello(self):
			return 'welcome'

		def echo(self, *args):
			return args
	
	# example for async server
	#k = xmlrpc_server(myroot())
	#asyncore.loop()
	
	# example for forkfarm server
	k = xmlrpc_forkfarm(myroot())
	k.loop()

