#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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.


__doc__ = """Glasnost Virtual Hosts Server"""

__version__ = '$Revision$'[11:-2]


from fnmatch import fnmatch
import os
import sgmllib
import sys


glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
sys.path.insert(0, glasnostPythonDir)

import glasnost

from glasnost.common.VirtualHostsCommon import *
import glasnost.common.faults as faults
import glasnost.common.system as system
import glasnost.common.tools_new as commonTools
import glasnost.common.xhtmlgenerator as X

from glasnost.server.ObjectsServer import register, ObjectServerMixin, \
        AdminServerMixin, ObjectsServer, ObjectsVirtualServer
from glasnost.server.tools import *

from glasnost.proxy.DispatcherProxy import getApplicationToken, \
        registerDispatcherId, registerVirtualServer, \
        updateDispatcherIdProfiles


applicationName = 'VirtualHostsServer'
applicationRole = 'virtualhosts'
dispatcher = None


class AdminVirtualHosts(AdminServerMixin, AdminVirtualHostsCommon):
    pass
register(AdminVirtualHosts)


class VirtualHostXmlParser(sgmllib.SGMLParser):
    inCustomWeb = 0
    customWeb = None
    customWebs = None
    currentRole = None
    
    inLocale = 0
    locale = None

    def __init__(self, body):
        sgmllib.SGMLParser.__init__(self)
        self.customWebs = {}
        self.feed(body)

    def start_customweb(self, attrs):
        if attrs[0][0] == 'role':
            self.currentRole = str(attrs[0][1])
            self.inCustomWeb = 1
            self.customWeb = ''
    
    def end_customweb(self):
        if self.inCustomWeb:
            self.inCustomWeb = 0
            self.customWebs[self.currentRole] = self.customWeb

    def start_locale(self, attrs):
        self.inLocale = 1
        self.locale = ''

    def end_locale(self):
        self.locale = self.locale.strip()
        self.inLocale = 0

    def handle_data(self, data):
        if self.inCustomWeb:
            self.customWeb += data
        if self.inLocale:
            self.locale += data


class VirtualHost(ObjectServerMixin, VirtualHostCommon):
    def acquireNonCore(self, objectDirectoryPath = None,
                       dataDirectoryPath = None, parentSlot = None):
        ObjectServerMixin.acquireNonCore(
            self, objectDirectoryPath = objectDirectoryPath,
            dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
        self.customWebs = {}
        self.locales = []
        profilesPath = os.path.join(commonTools.configDir, 'profiles')
        for p in self.profiles or []:
            pFileName = os.path.join(profilesPath, p + '.xml')
            vhP = VirtualHostXmlParser(open(pFileName).read())
            self.customWebs.update(vhP.customWebs)
            if vhP.locale:
                self.locales.append(vhP.locale)

    def checkModifyIsPossible(self, changes, givenSlotNames = None):
        ObjectServerMixin.checkModifyIsPossible(
            self, changes, givenSlotNames = givenSlotNames)
        objectsByHostName = self.getServer().virtualServer.objectsByHostName
        if (not givenSlotNames or 'hostName' in givenSlotNames) \
               and changes.hostName != self.hostName \
               and changes.hostName is not None:
            if objectsByHostName.has_key(changes.hostName) \
               and changes.id != objectsByHostName[changes.hostName].id:
                raise faults.DuplicateHostName(changes.hostName)

    def clear(self):
        objectsByHostName = self.getServer().virtualServer.objectsByHostName
        if objectsByHostName.has_key(self.hostName):
            del objectsByHostName[self.hostName]

    def modify(self, changes, givenSlotNames = None):
        objectsByHostName = self.getServer().virtualServer.objectsByHostName
        hostName = self.hostName
        ObjectServerMixin.modify(self, changes,
                                 givenSlotNames = givenSlotNames)
        if self.hostName != hostName:
            if hostName is not None:
                del objectsByHostName[hostName]
            if self.hostName is not None:
                objectsByHostName[self.hostName] = self

    def releaseNonCore(self, parentSlot = None):
        # [NOTAFIX] disabled the four next lines since acquireNonCore/use/
        # releaseNonCore is not atomic and we would sometimes end up
        # releasing while another process is in acquire
        #
        #if self.__dict__.has_key('customWebs'):
        #    del self.customWebs
        #if self.__dict__.has_key('locales'):
        #    del self.locales
        ObjectServerMixin.releaseNonCore(self, parentSlot = parentSlot)

register(VirtualHost)


class VirtualHostsVirtualServer(ObjectsVirtualServer):
    objectsByHostName = None
        
    def init(self):
        ObjectsVirtualServer.init(self)
        self.objectsByHostName = {}

    def initFromOldData(self, data):
        ObjectsVirtualServer.initFromOldData(self, data)
        self.objectsByHostName = {}
        for object in self.objects.values():
            if object.hostName is not None:
                self.objectsByHostName[object.hostName] = object


class VirtualHostsServer(VirtualHostsCommonMixin, ObjectsServer):
    VirtualServer = VirtualHostsVirtualServer
    hasMultipleVirtualServers = 0

    def addObjectXmlRpc(self, objectImport):
        """Create a new virtual host on the server.
        
        Keyword argument:
        =================

        *objectImport*:
            The new object in XML RPC dictionnary format.
        
        Returns:
        ========

        The new virtual host ID.
        
        Exceptions:
        ===========

        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *standard Exception*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        *KeyError*:
            + The virtual server ID does not correspond to a instanciated
              virtual server.
            + No client ID corresponding to the client token.

        *AttributeError*:
            The default dispatcherId contained in the object is not a string.

        *faults.UnknownApplicationToken*:
            The given application token is not in the dispatcher virtual server
            ids dictionnary.

        *OSError*:
            The apache configuration directory does not exists and cannot be
            created.

        *IOError*:
            The vhost file cannot be writen, or the templace config file cannot
            be read.

        """

        objectId = ObjectsServer.addObjectXmlRpc(self, objectImport)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        if not object.defaultDispatcherId:
            # we remove it before it harms somebody (actually it should have
            # been detected by ObjectsServer.addObjectXmlRpc)
            del virtualServer.objects[objectId]
            virtualServer.markObjectAsDeleted(objectId)
            virtualServer.markCoreAsDirty()
            raise faults.BadValue()

        if not object.profiles:
            object.profiles = ['basic', 'cms', 'vote']
            virtualServer.markObjectAsDirty(object)

        if virtualServer.objectsByHostName.has_key(object.hostName):
            # HostName already used.
            del virtualServer.objects[objectId]
            virtualServer.markObjectAsDeleted(objectId)
            virtualServer.markCoreAsDirty()
            raise faults.DuplicateHostName(object.hostName)

        virtualServer.objectsByHostName[object.hostName] = object
        virtualServer.markCoreAsDirty()

        if object.defaultDispatcherId[-1] == '/':
            object.defaultDispatcherId = object.defaultDispatcherId[:-1]
            virtualServer.markObjectAsDirty(object)

        newDispatcherId = object.defaultDispatcherId
        registerDispatcherId(newDispatcherId, object.profiles)
        newVirtualServerId = commonTools.makeApplicationId(
                newDispatcherId, self.applicationRole)
        context.push(
                applicationId = newVirtualServerId,
                )
        context.getVar('applicationTokens')[newVirtualServerId] = \
                getApplicationToken()
        context.pull()
        registerVirtualServer(self.hostName, self.port, newVirtualServerId)

        self.updateApacheVHost(object)
        return objectId

    def canAddObject(self):
        if context.getVar('noVirtualHost', default = 0):
            return 1
        return ObjectsServer.canAddObject(self)

    def convertVirtualServersIds(self, sourceDispatcherId,
                                 destinationDispatcherId):
        self.virtualServer.convertIds(
                sourceDispatcherId, destinationDispatcherId)
        self.virtualServer.markAllAsDirtyFIXME()
        return None

    def exportVirtualServer(self, virtualServerId, exportDirectoryPath):
        return None

    def getHostNameXmlRpc(self):
        """Return the url of the virtual server."""
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        dispatcherId = commonTools.extractDispatcherId(
                virtualServerId).lower()
        for object in virtualServer.objects.values():
            if dispatcherId == commonTools.extractDispatcherId(
                        object.defaultDispatcherId).lower():
                return utf8(object.hostName)
        raise faults.MissingItem(dispatcherId)

    def getObjectByHostNameXmlRpc(self, hostName):
        hostName = iso8859_15(hostName)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        objectId = self.getObjectIdByHostName(hostName)
        object = virtualServer.loadObjectCore(objectId)
        virtualServer.lock.acquire()
        object.acquireNonCore()
        try:
            result = object.exportToXmlRpc()
        finally:
            object.releaseNonCore()
        virtualServer.lock.release()
        return result

    def getObjectIdByHostName(self, hostName):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        askedHostName = hostName
        if not virtualServer.objectsByHostName:
            # There is no virtual host defined. Create one with the requested
            # host name.
            object = commonTools.newThing('object', 'virtualhosts.VirtualHost')
            object.title = 'Glasnost'
            object.hostName = hostName
            object.defaultDispatcherId = 'glasnost://%s' % hostName
            object.language = 'en'
            object.writersSet = [system.generalPublicId]
            object.readersSet = [system.generalPublicId]
            context.push(noVirtualHost = 1)
            try:
                self.addObjectXmlRpc(object.exportToXmlRpc())
            finally:
                context.pull()
        if not virtualServer.objectsByHostName.has_key(hostName):
            # hostNames, without eventual www.
            hostNames = virtualServer.objectsByHostName.keys()
            hostNames.sort(lambda x,y: -cmp(len(x), len(y)))
            for k in hostNames:
                if fnmatch(hostName, '*.'+k):
                    hostName = k
                    break
            else:
                if not virtualServer.admin.defaultVirtualHostId:
                    raise faults.MissingItem(hostName)
                try:
                    object = virtualServer.objects[
                            virtualServer.admin.defaultVirtualHostId]
                except KeyError:
                    raise faults.MissingItem(hostName)
                return object.id
        object = virtualServer.objectsByHostName[hostName]
        return object.id

    def getObjectIdByHostNameXmlRpc(self, hostName):
        hostName = iso8859_15(hostName)
        return self.getObjectIdByHostName(hostName)

    def hasDispatcherIdXmlRpc(self, dispatcherId):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        dispatcherId = iso8859_15(dispatcherId)
        for object in virtualServer.objects.values():
            if object.defaultDispatcherId == dispatcherId:
                return 1
        return 0

    def hasHostNameXmlRpc(self, hostName):
        # note that this method may return false while getObjectByHostName
        # returns something
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        hostName = iso8859_15(hostName)
        if virtualServer.objectsByHostName.has_key(hostName):
            return 1
        #hostNames = virtualServer.objectsByHostName.keys()
        #hostNames.sort(lambda x,y: -cmp(len(x), len(y)))
        #for k in hostNames:
        #    if fnmatch(hostName, '*.'+k):
        #        return 1
        return 0

    def importVirtualServer(self, virtualServerId, importDirectoryPath):
        return None

    def isAdmin(self):
        if context.getVar('noVirtualHost', default = 0):
            return 1
        return ObjectsServer.isAdmin(self)

    def modifyObjectXmlRpc(self, objectImport):
        id = objectImport['id']
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        oldObject = virtualServer.loadObjectCore(id)
        if oldObject.profiles:
            oldObjectProfiles = oldObject.profiles[:]
        else:
            oldObjectProfiles = []
        version = ObjectsServer.modifyObjectXmlRpc(self, objectImport)
        object = virtualServer.loadObjectCore(id)
        oldObjectProfiles.sort()
        object.profiles.sort()
        self.updateApacheVHost(object)
        if oldObjectProfiles != object.profiles:
            updateDispatcherIdProfiles(object.defaultDispatcherId,
                    object.profiles)
            # update vhosts that share the same defaultDispatcherId
            for o in virtualServer.objects.values():
                if o.defaultDispatcherId == object.defaultDispatcherId:
                    o.version += 1
                    o.profiles = object.profiles[:]
                    o.saveNonCore()
                    self.updateApacheVHost(o)
        return version

    def registerPublicMethods(self):
        ObjectsServer.registerPublicMethods(self)
        self.registerPublicMethod('getHostName', self.getHostNameXmlRpc)
        self.registerPublicMethod('getObjectByHostName',
                                  self.getObjectByHostNameXmlRpc)
        self.registerPublicMethod('getObjectIdByHostName',
                                  self.getObjectIdByHostNameXmlRpc)
        self.registerPublicMethod('hasDispatcherId',
                                  self.hasDispatcherIdXmlRpc)
        self.registerPublicMethod('hasHostName', self.hasHostNameXmlRpc)

    def registerToDispatcher(self):
        ObjectsServer.registerToDispatcher(self)
        for virtualHost in self.virtualServer.objects.values():
            newDispatcherId = virtualHost.defaultDispatcherId
            registerDispatcherId(newDispatcherId, virtualHost.profiles)
            newVirtualServerId = commonTools.makeApplicationId(
                    newDispatcherId, self.applicationRole)
            context.push(
                    applicationId = newVirtualServerId,
                    )
            context.getVar('applicationTokens')[newVirtualServerId] = \
                    getApplicationToken()
            context.pull()
            registerVirtualServer(self.hostName, self.port, newVirtualServerId)

    def repairVirtualServer(self, virtualServer, version):
        changed = 0
        if version < 5001:
            changed = 1
            virtualServer.objectsByHostName = {}
            for object in virtualServer.objects.values():
                if object.hostName is not None:
                    virtualServer.objectsByHostName[object.hostName] = object
        if version < 5004:
            changed = virtualServer.admin.repair(5004) or changed
            for id, object in virtualServer.objects.items():
                changed = object.repair(5004) or changed
        if version <= 1009000:
            admin = virtualServer.admin
            adminId = '%s/%s/__admin__' % (
                    commonTools.extractDispatcherId(
                            virtualServer.virtualServerId),
                    self.applicationRole)
            if admin.id != adminId:
                changed = 1
                admin.id = adminId
        if version <= 1033000:
            for object in virtualServer.objects.values():
                if object.defaultDispatcherId[-1] == '/':
                    object.defaultDispatcherId = object.defaultDispatcherId[
                            :-1]
                    changed = 1

        if changed:
            virtualServer.markAllAsDirtyFIXME()

    def updateApacheVHost(self, object):
        """Update the apache virtual host configuration.

        Get the apache config template from the virtual host server data
        directory.
        Create the apache vhost files and fill them to add the given object as
        a new virtual host.

        Keyword argument
        ================

        *object*:
            The virtual host object instance to add to the apache virtual
            host(s).

        Exceptions
        ==========

        *OSError*:
            The apache configuration directory does not exists and cannot be
            created.

        *IOError*:
            The vhost file cannot be written, or the template config file
            cannot be read.

        """
        
        hostName = object.hostName
        templateFileName = '%s/%s/apache-template.conf' % (
                self.dataDirectoryPath, applicationRole)
        if not os.path.exists(templateFileName):
            return
        if not os.path.exists('%s/%s/apache' % (
                self.dataDirectoryPath, applicationRole)):
            try:
                os.mkdir('%s/%s/apache' % (
                        self.dataDirectoryPath, applicationRole))
            except OSError:
                return
            
        apacheConfFileName = '%s/%s/apache/vhost-%s.conf' % (
                self.dataDirectoryPath, applicationRole, hostName)
        try:
            open(apacheConfFileName, 'w').write(
                open(templateFileName).read() % {'hostName': hostName})
        except IOError:
            pass

    def upgradeVirtualServer_0001_0025(self, virtualServer):
        # Repair dissociated objects and objectsByHostName
        virtualServer.objectsByHostName = {}
        for object in virtualServer.objects.values():
            if object.hostName is not None:
                virtualServer.objectsByHostName[object.hostName] = object
        virtualServer.markCoreAsDirty()

    def upgradeVirtualServer_0001_0027(self, virtualServer):
        for object in virtualServer.objects.values():
            object.profiles = ['basic', 'cms', 'vote', 'translations']
        virtualServer.markCoreAsDirty()

virtualHostsServer = VirtualHostsServer()


if __name__ == "__main__":
    virtualHostsServer.launch(applicationName, applicationRole)

