# Copyright (c) 2001 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307
# USA.
__revision__ = "$Id: pyro.py,v 1.5 2002/02/14 16:51:58 alf Exp $"

from narval.communication.RPCFactory import ServerInterface, ProxyInterface,\
     decode_uri
from narval.services.BaseService import BaseService
import socket, time
from Pyro import naming, core, config
from Pyro.errors import PyroError, NamingError

GROUP = ':narval'

class PYROServer(core.Daemon, BaseService, ServerInterface):
    
    def __init__(self, port, method_handler, debug=0) :
        BaseService.__init__(self,  'PYRO')
        host = socket.gethostname()
        # init pyro daemon
        core.initServer()
        config.PYRO_NS_DEFAULTGROUP = GROUP
        #config.PYRO_NS_ROOTCHAR = ':'
        #config.PYRO_NS_GROUPSEP = '.'
        if debug:
            print 'PYRO DEBUG MODE ON'
            config.PYRO_LOGFILE = config.PYRO_USER_LOGFILE = '/tmp/narval%d-pyro.log'%port
            config.PYRO_TRACELEVEL = 3
            config.PYRO_USER_TRACELEVEL = 3
        core.Daemon.__init__(self, host=host, port=port)
        # get (start if necessary) the NS
        ns = get_pyro_ns()
        self.useNameServer(ns)
        # make sure our namespace group exists
        try:
            ns.createGroup(GROUP)
        except NamingError:
            pass
        # create dispatcher 
        dispatcher =  generate_dispatcher(method_handler, 'core.ObjBase')
        # register dispatcher object
        o_uri = encode_pyro_uri(host, port, method_handler.NAME)
        try:
            ns.unregister(o_uri)
        except NamingError:
            pass
        print 'register', o_uri
        self._o_uri = o_uri
        self.connect(dispatcher, o_uri)

    def __del__(self):
        ns = get_pyro_ns()
        ns.unregister(self._o_uri)

    def _run(self):
        while self.loop:
            self.handleRequests(1.0)

    
class PYROProxy(ProxyInterface):
    def __init__(self, uri):
        core.initClient()
        config.PYRO_NS_DEFAULTGROUP = GROUP
        # get (start if necessary) the pyro name server
        ns = get_pyro_ns()
	# resolve the pyro object
        type, host, port, obj_name = decode_uri(uri)
        uri = encode_pyro_uri(host, port, obj_name)
        pyro_uri = ns.resolve(uri)
        # retrieve proxy object
        self.obj = core.getProxyForURI(pyro_uri)

def encode_pyro_uri(host, port, name):
    host = host.split('.')[0]
    return '%s%s%s' % (host, port, name)

def get_pyro_ns():
    locator = naming.NameServerLocator()
    try:
        ns = locator.getNS()
    except PyroError:
        from os import spawnv, P_NOWAIT
        from sys import executable
        spawnv(P_NOWAIT , executable,
               (executable, '-c', 'import Pyro.naming; Pyro.naming.main()'))
        time.sleep(1)
        ns = locator.getNS(socket.gethostname())
    return ns

def generate_dispatcher(method_handler, parent_class=None):
    """
    Create a dispatcher class and return an instance of it from a dispatcher
    definition.
    The definition is a class with the following attributes:
    _ EXPORTED_METHOD: dictionary where keys are method names and values
      class attribute names of the attributes holding references to an object
      implementing the method 
    _ attributes defined in EXPORTED_METHODS values. They must contain an
      object instance which implements the respective methods (EXPORTED_METHODS
      keys)

    Ex:
    class TestDispatchHandler:
        EXPORTED_METHODS = {'method1': 'attr1',
                            'method2': 'attr1',
                            'method3': 'attr2'}
        attr1 = Object1()
        attr2 = Object2()

    where Object1 is a class which provides method1 and method2 and Object2 a
    class which provides method3

    obj_inst = generate_dispatcher(TestDispatchHandler)

    will affect in 'obj_inst' a class instance which provide method1, method2
    and method3 by delegate it to the correct object
    """
    # class definition
    if parent_class:
        class_str = 'class Dispatcher(%s):\n' % parent_class
        statements = '  %s.__init__(self)\n' % parent_class
    else:
        class_str = 'class Dispatcher:\n'
        statements = ''

    # methods definition
    registered = []
    for method, objname in method_handler.EXPORTED_METHODS.items():
        if not objname in registered:
            registered.append(objname)
        class_str = '%s def %s(self, *attrs):\n  return self.%s.%s(*attrs)\n'%\
                    (class_str, method, objname, method)

    # constructor definition
    attrs = ''
    for objname in registered:
        attrs = '%s, %s' % (attrs, objname)
        statements = '%s  self.%s=%s\n' % (statements, objname, objname)
        # retrieve object reference in current context
        exec '%s=getattr(method_handler, "%s")'%(objname, objname)

    # assemble all parts
    class_str = '%s def __init__(self%s):\n%s' % (class_str, attrs, statements)

    # now we can eval the full class
    exec class_str

    # return an instance of constructed class
    return eval('Dispatcher(%s)'%attrs[2:]) # attrs[2:] for removing ', '
    
