# -*- test-case-name: twisted.test.test_application -*-

# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
# See LICENSE for details.

#
"""Backwards compatibility module

This module allows Applications to behave (partially) like old Application
objects, and converts olds Applications to new ones. 

API Stability: unstable

Maintainer: U{Moshe Zadka<mailto:moshez@twistedmatrix.com>}
"""

from twisted.python import components
from twisted.application import internet, service
from twisted.persisted import sob
import warnings, sys
from zope.interface import implements


class IOldApplication(components.Interface):

    """A subset of the interface old Application objects had implicitly

    This interface defines a subset of the interface old Application
    objects had, so that new objects can support it for compatibility
    with old code
    """
    def listenWith(self, portType, *args, **kw):
        """Add a service that starts an instance of C{portType} listening.

        @type portType: type which implements C{IListeningPort}
        @param portType: The object given by C{portType(*args, **kw)}
        will be started listening.
        """

    def listenTCP(self, port, factory, backlog=5, interface=''):
        """Add a service that connects a given protocol factory to the port.

        @param port: a port number on which to listen

        @param factory: a twisted.internet.protocol.ServerFactory instance

        @param backlog: size of the listen queue

        @param interface: the hostname to bind to, defaults to '' (all)
        """

    def listenUNIX(self, filename, factory, backlog=5, mode=0666):
        """Add a service that listens on a UNIX socket.

        @param address: a path to a unix socket on the filesystem.

        @param factory: a L{twisted.internet.protocol.Factory} instance.

        @param backlog: number of connections to allow in backlog.

        @param mode: mode to set on the unix socket.
        """

    def listenUDP(self, port, proto, interface='', maxPacketSize=8192):
        """Add a service that connects a given DatagramProtocol to the port.
        """

    def listenSSL(self, port, factory, ctxFactory, backlog=5, interface=''):
        """Add a service that connects a given protocol factory to the port.

        The connection is a SSL one, using contexts created by the context
        factory.

        @param port: a port number on which to listen

        @param factory: a L{twisted.internet.protocol.ServerFactory} instance

        @param contextFactory: a L{twisted.internet.ssl.ContextFactory} instance

        @param backlog: size of the listen queue

        @param interface: the hostname to bind to, defaults to '' (all)
        """

    def connectWith(self, connectorType, *args, **kw):
        """Add a service that starts an instance of C{connectorType} connecting.

        @type connectorType: type which implements C{IConnector}
        @param connectorType: The object given by C{connectorType(*args, **kw)}
        will be started connecting.
        """

    def connectUDP(self, remotehost, remoteport, protocol, localport=0,
                  interface='', maxPacketSize=8192):
        """Add a service that connects a L{ConnectedDatagramProtocol} to a port.
        """

    def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
        """Add a service that connects a TCP client.

        @param host: a host name

        @param port: a port number

        @param factory: a twisted.internet.protocol.ClientFactory instance

        @param timeout: number of seconds to wait before assuming the
                        connection has failed.

        @param bindAddress: a (host, port) tuple of local address to bind
                            to, or None.
        """

    def connectSSL(self, host, port, factory, ctxFactory, timeout=30,
                   bindAddress=None):
        """Add a service that connects a client Protocol to a remote SSL socket.

        @param host: a host name

        @param port: a port number

        @param factory: a L{twisted.internet.protocol.ClientFactory} instance

        @param contextFactory: a L{twisted.internet.ssl.ClientContextFactory}

        @param timeout: number of seconds to wait before assuming the connection
            has failed

        @param bindAddress: a (host, port) tuple of local address to bind to, or
            C{None}
        """

    def connectUNIX(self, address, factory, timeout=30):
        """Add a service that connects a client protocol to a UNIX socket.

        @param address: a path to a unix socket on the filesystem.

        @param factory: a L{twisted.internet.protocol.ClientFactory} instance

        @param timeout: number of seconds to wait before assuming the connection
            has failed.
        """

    def addService(self, service):
        """Add a service to this collection.
        """

    def getServiceNamed(self, name):
        """Retrieve the named service from this application.

        Raise a KeyError if there is no such service name.
        """

    def removeService(self, service):
        """Remove a service from this collection."""

    def unlistenWith(self, portType, *args, **kw):
        """Maybe remove a listener

        This function is inherently unreliable, and may or may
        not remove a service.
        """

    def unlistenTCP(self, port, interface=''):
        """Maybe remove a listener

        This function is inherently unreliable, and may or may
        not remove a service.
        """

    def unlistenUNIX(self, filename):
        """Maybe remove a listener

        This function is inherently unreliable, and may or may
        not remove a service.
        """

    def unlistenUDP(self, port, interface=''):
        """Maybe remove a listener

        This function is inherently unreliable, and may or may
        not remove a service.
        """

    def unlistenSSL(self, port, interface=''):
        """Maybe remove a listener

        This function is inherently unreliable, and may or may
        not remove a service.
        """


class _NewService:
    """Wrap a twisted.internet.app.ApplicationService in new service API."""

    implements(service.IService)

    running = 0
    
    def __init__(self, service):
        self.service = service
        self.name = service.serviceName

    def setName(self, name):
        raise RuntimeError

    def setServiceParent(self, parent):
        self.service.setServiceParent(parent)

    def disownServiceParent(self):
        self.service.disownServiceParent()

    def startService(self):
        self.running = 1
        self.service.startService()

    def stopService(self):
        self.running = 0
        return self.service.stopService()

    def privilegedStartService(self):
        pass

    def get_name(self):
        return self.service.serviceName

    name = property(get_name)
    del get_name
    
    def __cmp__(self, other):
        return cmp(self.service, other)

    def __hash__(self):
        return hash(self.service)


class _ServiceNetwork:

    implements(IOldApplication)

    def __init__(self, app):
        self.app = service.IServiceCollection(app)

    def listenWith(self, portType, *args, **kw):
        s = internet.GenericServer(portType, *args, **kw)
        s.privileged = 1
        s.setServiceParent(self.app)

    def listenTCP(self, port, factory, backlog=5, interface=''):
        s = internet.TCPServer(port, factory, backlog, interface)
        s.privileged = 1
        s.setServiceParent(self.app)

    def listenUNIX(self, filename, factory, backlog=5, mode=0666):
        s = internet.UNIXServer(filename, factory, backlog, mode)
        s.privileged = 1
        s.setServiceParent(self.app)

    def listenUDP(self, port, proto, interface='', maxPacketSize=8192):
        s = internet.UDPServer(port, proto, interface, maxPacketSize)
        s.privileged = 1
        s.setServiceParent(self.app)

    def listenSSL(self, port, factory, ctxFactory, backlog=5, interface=''):
        s = internet.SSLServer(port, factory, ctxFactory, backlog, interface)
        s.privileged = 1
        s.setServiceParent(self.app)

    def connectWith(self, connectorType, *args, **kw):
        s = internet.GenericClient(connectorType,  *args, **kw)
        s.setServiceParent(self.app)

    def connectUDP(self, remotehost, remoteport, protocol, localport=0,
                  interface='', maxPacketSize=8192):
        s = internet.UDPClient(remotehost, remoteport, protocol, localport,
                               interface, maxPacketSize)
        s.setServiceParent(self.app)

    def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
        s = internet.TCPClient(host, port, factory, timeout, bindAddress)
        s.setServiceParent(self.app)

    def connectSSL(self, host, port, factory, ctxFactory, timeout=30,
                   bindAddress=None):
        s = internet.SSLClient(host, port, factory, ctxFactory, timeout,
                               bindAddress)
        s.setServiceParent(self.app)

    def connectUNIX(self, address, factory, timeout=30):
        s = internet.UNIXClient(address, factory, timeout)
        s.setServiceParent(self.app)

    def addService(self, service):
        if 'twisted.internet.app' in sys.modules:
            from twisted.internet import app as oldapp
            if isinstance(service, oldapp.ApplicationService):
                service = _NewService(service)
        self.app.addService(service)

    def removeService(self, service):
        if 'twisted.internet.app' in sys.modules:
            from twisted.internet import app as oldapp
            if isinstance(service, oldapp.ApplicationService):
                service = _NewService(service)
        self.app.removeService(service)

    def getServiceNamed(self, name):
        result = self.app.getServiceNamed(name)
        if isinstance(result, _NewService):
            result = result.service
        return result

    def unlistenWith(self, portType, *args, **kw):
        warnings.warn("unlisten* does not work anymore. Name services "
                      "that you want to be able to remove",
                      category=RuntimeWarning)

    def unlistenTCP(self, port, interface=''):
        warnings.warn("unlisten* does not work anymore. Name services "
                      "that you want to be able to remove",
                      category=RuntimeWarning)

    def unlistenUNIX(self, filename):
        warnings.warn("unlisten* does not work anymore. Name services "
                      "that you want to be able to remove",
                      category=RuntimeWarning)

    def unlistenUDP(self, port, interface=''):
        warnings.warn("unlisten* does not work anymore. Name services "
                      "that you want to be able to remove",
                      category=RuntimeWarning)

    def unlistenSSL(self, port, interface=''):
        warnings.warn("unlisten* does not work anymore. Name services "
                      "that you want to be able to remove",
                      category=RuntimeWarning)


components.registerAdapter(_ServiceNetwork,
                           service.IServiceCollection, IOldApplication)
components.backwardsCompatImplements(_ServiceNetwork)
components.backwardsCompatImplements(_NewService)


_mapping = []
for tran in 'tcp unix udp ssl'.split():
    _mapping.append((tran+'Ports', getattr(internet, tran.upper()+'Server')))
    _mapping.append((tran+'Connectors',getattr(internet,tran.upper()+'Client')))

def convert(oldApp):
    '''Convert an C{i.app.Application} to a C{application.service.Application}

    @type oldApp: C{twisted.internet.app.Application}
    @rtype C{twisted.application.service.Application}

    This function might damage oldApp beyond repair: services
    that other parts might be depending on might be missing.
    It is not safe to use oldApp after it has been converted.
    In case this behaviour is not desirable, pass a deep copy
    of the old application
    '''
    ret = service.Application(oldApp.name, getattr(oldApp, "uid", None), getattr(oldApp, "gid", None))
    c = service.IServiceCollection(ret)
    service.IProcess(ret).processName = oldApp.processName
    for (pList, klass) in [(oldApp.extraPorts, internet.GenericServer),
                           (oldApp.extraConnectors, internet.GenericClient),]:
        for (portType, args, kw) in pList:
            klass(portType, *args, **kw).setServiceParent(c)
    for (name, klass) in _mapping:
        for args in getattr(oldApp, name):
            klass(*args).setServiceParent(c)
    for s in c:
        if hasattr(s, 'privileged'):
            s.privileged = 1
    for s in oldApp.services.values():
        if not service.IService.providedBy(s):
            s.serviceParent = None
            s = _NewService(s)
            s.setServiceParent(IOldApplication(c))
        else:
            s.serviceParent = None
            s.setServiceParent(c)
    return ret


__all__ = ['IOldApplication', 'convert']
