#!/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 Articles Server"""

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


import base64
import fcntl
import os
import sys
import time
import re

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

import glasnost

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

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

from glasnost.proxy.CacheProxy import invalidateValue
from glasnost.proxy.DispatcherProxy import MultiCall, \
        getApplicationId, getApplicationToken
from glasnost.proxy.GroupsProxy import getSetContainedIds


applicationName = 'ArticlesServer'
applicationRole = 'articles'
dispatcher = None


class AdminArticles(AdminServerMixin, AdminArticlesCommon):
    pass
register(AdminArticles)


class Article(ObjectServerMixin, ArticleCommon):
    def acquireNonCore(self, objectDirectoryPath = None,
                       dataDirectoryPath = None, parentSlot = None):
        ObjectServerMixin.acquireNonCore(
            self, objectDirectoryPath = objectDirectoryPath,
            dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
        self.loadBody()

    def getDocBookChapter(self):
        # TODO: support conversion from every text format to DocBook
        assert self.format == 'spip'
        from glasnost.proxy.tools import makeDocBookFromSpip
        return makeDocBookFromSpip(self.body, docType = 'chapter',
                                   addHeader = 0, title = self.title)

    def getLatexChapter(self):
        # TODO: support conversion from every text format to LaTeX
        assert self.format == 'spip'
        from glasnost.proxy.tools import makeLatexFromSpip
        return makeLatexFromSpip(self.body, docType = 'chapter',
                                 addHeader = 0, title = self.title)
        
    def loadBody(self):
        virtualServerId = self.getServer().computeVirtualServerId(self.id)
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        articlesDirectoryPath = os.path.join(
            virtualServer.dataDirectoryPath, self.getServer().applicationRole)
        serverHostNameAndPort, serverRole, localId = \
                commonTools.splitId(self.id)
        bodyFilePath = os.path.join(articlesDirectoryPath, localId)
        try:
            bodyFile = open(bodyFilePath, 'rb')
        except IOError:
            if self.__dict__.has_key('body'):
                del self.body
        else:
            fcntl.lockf(bodyFile, fcntl.LOCK_SH)
            self.body = bodyFile.read()
            fcntl.lockf(bodyFile, fcntl.LOCK_UN)
            bodyFile.close()

    def modify(self, changes, givenSlotNames = None):
        if (not givenSlotNames or 'body' in givenSlotNames) \
               and changes.body != self.body:
            if changes.body is None:
                if self.lastEditorId:
                    del self.lastEditorId
                if self.editionTime:
                    del self.editionTime
            else:
                userId = getProxyForServerRole('identities').getUserId()
                if userId:
                    self.lastEditorId = userId
                self.editionTime = time.time()
        ObjectServerMixin.modify(
            self, changes, givenSlotNames = givenSlotNames)

    def releaseNonCore(self, parentSlot = None):
        if self.__dict__.has_key('body'):
            del self.body
        ObjectServerMixin.releaseNonCore(self, parentSlot = parentSlot)

    def removeBodyFile(self):
        virtualServerId = self.getServer().computeVirtualServerId(self.id)
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        articlesDirectoryPath = os.path.join(
            virtualServer.dataDirectoryPath, self.getServer().applicationRole)
        serverHostNameAndPort, serverRole, localId = \
                commonTools.splitId(self.id)
        bodyFilePath = os.path.join(articlesDirectoryPath, localId)
        if os.path.exists(bodyFilePath):
            os.remove(bodyFilePath)

    def removeNonCore(self, objectDirectoryPath = None,
                      dataDirectoryPath = None, parentSlot = None):
        ObjectServerMixin.removeNonCore(
            self, objectDirectoryPath = objectDirectoryPath,
            dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
        self.removeBodyFile()

    def saveBody(self):
        if self.body is None:
            self.removeBodyFile()
            return
        virtualServerId = self.getServer().computeVirtualServerId(self.id)
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        articlesDirectoryPath = os.path.join(
            virtualServer.dataDirectoryPath, self.getServer().applicationRole)
        if not os.access(virtualServer.dataDirectoryPath, os.F_OK):
            os.mkdir(virtualServer.dataDirectoryPath)
            os.chmod(virtualServer.dataDirectoryPath, 0750)
        if not os.access(articlesDirectoryPath, os.F_OK):
            os.mkdir(articlesDirectoryPath)
            os.chmod(articlesDirectoryPath, 0750)
        serverHostNameAndPort, serverRole, localId = \
                commonTools.splitId(self.id)
        bodyFilePath = os.path.join(articlesDirectoryPath, localId)
        bodyFile = open(bodyFilePath, 'wb')
        os.chmod(bodyFilePath, 0640)
        fcntl.lockf(bodyFile, fcntl.LOCK_EX)
        bodyFile.write(self.body)
        fcntl.lockf(bodyFile, fcntl.LOCK_UN)
        bodyFile.close()

    def saveNonCore(self, objectDirectoryPath = None, dataDirectoryPath = None,
                    parentSlot = None):
        ObjectServerMixin.saveNonCore(
            self, objectDirectoryPath = objectDirectoryPath,
            dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
        self.saveBody()
register(Article)


class ArticlesServer(ArticlesCommonMixin, ObjectsServer):
    def addObjectXmlRpc(self, objectImport):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = commonTools.importThing(objectImport)
        # FIXME: I'd better understand this test
        if not self.canAddObject() or (
               not self.isAdmin() and not (
                getProxyForServerRole('identities').setContainsUser(
                self.getAdminCore(check = 0).writersSet)
                and getProxyForServerRole('identities').setContainsUser(
                object.writersSet))):
            if not object.canBeCreatedByClient():
                raise faults.UserAccessDenied()
        object.checkAddIsPossible()
        object.setAutomaticalSlots()
        virtualServer.objects[object.id] = object
        if object.body is not None:
            userId = getProxyForServerRole('identities').getUserId()
            if userId:
                object.lastEditorId = userId
            object.editionTime = object.modificationTime
        object.saveNonCore()
        object.releaseNonCore()
        virtualServer.markObjectAsDirty(object)
        virtualServer.markCoreAsDirty()
        return object.id

    def convertVirtualServersIds(
            self, sourceDispatcherId, destinationDispatcherId):
        exitCode = ObjectsServer.convertVirtualServersIds(
            self, sourceDispatcherId, destinationDispatcherId)
        if exitCode is not None:
            return exitCode
        destinationVirtualServerId = '%s/%s' % (
            destinationDispatcherId, self.applicationRole)
        virtualServer = self.virtualServers[destinationVirtualServerId]
        sourceHostName = extractApplicationHostName(sourceDispatcherId)
        destinationHostName = extractApplicationHostName(
            destinationDispatcherId)
        sourceVirtualServerDataDirectoryPath = os.path.join(
            self.dataDirectoryPath, sourceHostName)
        destinationVirtualServerDataDirectoryPath = os.path.join(
            self.dataDirectoryPath, destinationHostName)
        sourceObjectsDirectoryPath = os.path.join(
            sourceVirtualServerDataDirectoryPath, self.applicationRole)
        destinationObjectsDirectoryPath = os.path.join(
            destinationVirtualServerDataDirectoryPath, self.applicationRole)
        for id in virtualServer.objects.keys():
            serverHostNameAndPortNotUsed, serverRoleNotUsed, localId = \
                    commonTools.splitId(id)
            sourceBodyFilePath = os.path.join(
                sourceObjectsDirectoryPath, localId)
            destinationBodyFilePath = os.path.join(
                destinationObjectsDirectoryPath, localId)
            if not os.access(sourceBodyFilePath, os.F_OK):
                continue
            if not os.access(
                destinationVirtualServerDataDirectoryPath, os.F_OK):
                os.mkdir(destinationVirtualServerDataDirectoryPath)
                os.chmod(destinationVirtualServerDataDirectoryPath, 0750)
            if not os.access(destinationObjectsDirectoryPath, os.F_OK):
                os.mkdir(destinationObjectsDirectoryPath)
                os.chmod(destinationObjectsDirectoryPath, 0750)
            sourceBodyFile = open(sourceBodyFilePath, 'rb')
            destinationBodyFile = open(destinationBodyFilePath, 'wb')
            os.chmod(destinationBodyFilePath, 0640)
            destinationBodyFile.write(sourceBodyFile.read())
            sourceBodyFile.close()
            destinationBodyFile.close()
        return None

    def exportVirtualServer(self, virtualServerId, exportDirectoryPath):
        exitCode = ObjectsServer.exportVirtualServer(
            self, virtualServerId, exportDirectoryPath)
        if exitCode is not None:
            return exitCode
        virtualServer = self.virtualServers[virtualServerId]
        hostName = extractApplicationHostName(virtualServerId)
        virtualServerDataDirectoryPath = os.path.join(
            self.dataDirectoryPath, hostName)
        objectsDirectoryPath = os.path.join(
            virtualServerDataDirectoryPath, self.applicationRole)
        exportObjectsDirectoryPath = os.path.join(
            exportDirectoryPath, self.applicationRole)
        for id in virtualServer.objects.keys():
            serverHostNameAndPortNotUsed, serverRoleNotUsed, localId = \
                    commonTools.splitId(id)
            sourceBodyFilePath = os.path.join(
                objectsDirectoryPath, localId)
            exportBodyFilePath = os.path.join(
                exportObjectsDirectoryPath, localId)
            if not os.access(sourceBodyFilePath, os.F_OK):
                continue
            if not os.access(exportDirectoryPath, os.F_OK):
                os.mkdir(exportDirectoryPath)
                os.chmod(exportDirectoryPath, 0750)
            if not os.access(exportObjectsDirectoryPath, os.F_OK):
                os.mkdir(exportObjectsDirectoryPath)
                os.chmod(exportObjectsDirectoryPath, 0750)
            sourceBodyFile = open(sourceBodyFilePath, 'rb')
            exportBodyFile = open(exportBodyFilePath, 'wb')
            os.chmod(exportBodyFilePath, 0640)
            exportBodyFile.write(sourceBodyFile.read())
            sourceBodyFile.close()
            exportBodyFile.close()
        return None

    def getLastObjectIds(self, objectsCount, possibleAuthorsSet,
                         possibleReadersSet, possibleWritersSet):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        isAdmin = self.isAdmin()
        try:
            possibleAuthorIds = getSetContainedIds(
                    possibleAuthorsSet, ['people'], raiseWhenUncountable = 1)
        except faults.UncountableGroup:
            possibleAuthorIds = 'everybody'
        try:
            possibleReaderIds = getSetContainedIds(
                    possibleReadersSet, ['identities'],
                    raiseWhenUncountable = 1)
        except faults.UncountableGroup:
            possibleReaderIds = 'everybody'
        try:
            possibleWriterIds = getSetContainedIds(
                    possibleWritersSet, ['identities'],
                    raiseWhenUncountable = 1)
        except faults.UncountableGroup:
            possibleWriterIds = 'everybody'
        objectIds = virtualServer.objects.keys()
        def modificationTimeSorter(xId, yId,
                                   virtualServer = virtualServer):
            return cmp(virtualServer.loadObjectCore(yId).modificationTime,
                       virtualServer.loadObjectCore(xId).modificationTime)
        objectIds.sort(modificationTimeSorter)
        result = []
        for objectId in objectIds:
            object = virtualServer.loadObjectCore(objectId)
            if not isAdmin and not getProxyForServerRole(
                    'identities').setContainsUser(object.readersSet):
                continue
            if not self.getLastObjectIds_filter(
                    possibleAuthorIds, 1, object.authorsSet):
                continue
            if not self.getLastObjectIds_filter(
                    possibleReaderIds, 1, object.readersSet):
                continue
            if not self.getLastObjectIds_filter(
                    possibleWriterIds, 1, object.writersSet):
                continue
            result.append(objectId)
            if objectsCount != -1 and len(result) >= objectsCount:
                break
        return result

    def getObjectDocBookChapterXmlRpc(self, objectId):
        object = self.getObjectCore(objectId)
        object.acquireNonCore()
        try:
            result = object.getDocBookChapter()
        finally:
            object.releaseNonCore()
        return utf8(result)

    def getObjectLatexChapterXmlRpc(self, objectId):
        object = self.getObjectCore(objectId)
        object.acquireNonCore()
        try:
            result = object.getLatexChapter()
        finally:
            object.releaseNonCore()
        return utf8(result)

    def importVirtualServer(self, virtualServerId, importDirectoryPath):
        virtualServer = ObjectsServer.importVirtualServer(
            self, virtualServerId, importDirectoryPath)
        if virtualServer is None:
            return None
        hostName = extractApplicationHostName(virtualServerId)
        virtualServerDataDirectoryPath = os.path.join(
            self.dataDirectoryPath, hostName)
        importObjectsDirectoryPath = os.path.join(
            importDirectoryPath, self.applicationRole)
        objectsDirectoryPath = os.path.join(
            virtualServerDataDirectoryPath, self.applicationRole)
        for id in virtualServer.objects.keys():
            serverHostNameAndPortNotUsed, serverRoleNotUsed, localId = \
                    commonTools.splitId(id)
            importBodyFilePath = os.path.join(
                importObjectsDirectoryPath, localId)
            destinationBodyFilePath = os.path.join(
                objectsDirectoryPath, localId)
            if not os.access(importBodyFilePath, os.F_OK):
                continue
            if not os.access(virtualServerDataDirectoryPath, os.F_OK):
                os.mkdir(virtualServerDataDirectoryPath)
                os.chmod(virtualServerDataDirectoryPath, 0750)
            if not os.access(objectsDirectoryPath, os.F_OK):
                os.mkdir(objectsDirectoryPath)
                os.chmod(objectsDirectoryPath, 0750)
            importBodyFile = open(importBodyFilePath, 'rb')
            destinationBodyFile = open(destinationBodyFilePath, 'wb')
            os.chmod(destinationBodyFilePath, 0640)
            destinationBodyFile.write(importBodyFile.read())
            importBodyFile.close()
            destinationBodyFile.close()
        return virtualServer

    def registerPublicMethods(self):
        ObjectsServer.registerPublicMethods(self)
        self.registerPublicMethod('getObjectDocBookChapter',
                                         self.getObjectDocBookChapterXmlRpc)
        self.registerPublicMethod('getObjectLatexChapter',
                                         self.getObjectLatexChapterXmlRpc)
        self.registerPublicMethod('search')

    def repairVirtualServer(self, virtualServer, version):
        changed = 0
        if version < 3000:
            for object in virtualServer.objects.values():
                if object.body is not None:
                    changed = 1
                    object.saveBody()
                    del object.body
                    object.lastEditorId = 'eePeopleServer/8' # = Manou
                    object.editionTime = object.modificationTime
                elif object.__dict__.has_key('body'):
                    changed = 1
                    del object.body
                if object.__dict__.has_key('state'):
                    changed = 1
                    object.writersSet = object.authorsSet
                    if object.writersSet is not None:
                        object.writersSet = object.writersSet[:]
                    if object.state != 'published':
                        object.readersSet = [
                            'eePeopleServer/1', # = Manou
                            ]
                    del object.state
        if version < 4000:
            changed = virtualServer.admin.repair(4000) or changed
            for id, object in virtualServer.objects.items():
                newId = repairId(id)
                if newId:
                    changed = 1
                    del virtualServer.objects[id]
                    virtualServer.objects[newId] = object
                changed = object.repair(4000) or changed
                if not object.__dict__.has_key('language'):
                    changed = 1
                    object.language = 'fr'
        if version < 5004:
            changed = virtualServer.admin.repair(5004) or changed
            hostName = extractApplicationHostName(
                virtualServer.virtualServerId)
            virtualServerDataDirectoryPath = os.path.join(
                self.dataDirectoryPath, hostName)
            objectsDirectoryPath = os.path.join(
                virtualServerDataDirectoryPath, self.applicationRole)
            for id, object in virtualServer.objects.items():
                changed = object.repair(5004) or changed
                serverHostNameAndPort, serverRole, localId = \
                        commonTools.splitId(id)
                bodyFilePath = os.path.join(objectsDirectoryPath, localId)
                if not os.access(bodyFilePath, os.F_OK):
                    continue
                file = open(bodyFilePath, 'rb')
                body = file.read()
                file.close()
                repairedBody = body.replace('\r\n', '\n')
                repairedBody = repairedBody.replace('\r', '\n')
                if repairedBody != body:
                    file = open(bodyFilePath, 'wb')
                    os.chmod(bodyFilePath, 0640)
                    file.write(repairedBody)
                    file.close()
        if version <= 1021000:
            admin = virtualServer.admin
            if admin.id is None:
                changed = 1
                admin.id = '%s/__admin__' % virtualServer.virtualServerId
        if changed:
            virtualServer.markAllAsDirtyFIXME()

    def search(self, searchTerms, scope, language):
        searchTerms = iso8859_15(searchTerms)

        serverId = getProxyForServerRole('translations').getServerId()
        applicationToken = getApplicationToken()

        params = []

        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        isAdmin = self.isAdmin()
        objectsLookedIn = []

        # FIXME: Do not translate here. Should be done by the client.
        multiCall = MultiCall()
        for objectId, object in virtualServer.objects.items():
            if not isAdmin \
               and not getProxyForServerRole('identities'
                       ).setContainsUser(object.readersSet):
                continue
            if 'body' in scope:
                object.loadBody()
            for s in scope:
                text = getattr(object, s)
                if not text:
                    continue
                sourcePath = 'self.%s' % s
                getProxyForServerRole('translations').getTranslationInfos(
                    text, object.id, sourcePath, object.language,
                    [language], ignoreNew = 1, multiCall = multiCall)
                objectsLookedIn.append(objectId)
            if object.__dict__.has_key('body'):
                del object.body
        lazyResults = multiCall.call()

        nbTerms = len(searchTerms.split(' '))
        regexpOne = re.compile(
            r'\b(%s)\b' % '|'.join([re.escape(x) for x in searchTerms.split(' ')]),
            re.LOCALE | re.IGNORECASE)
        regexpAll = re.compile(re.escape(searchTerms).replace(' ', '\s*'),
                               re.LOCALE | re.IGNORECASE)

        result = []
        scores = {}
        for i in range(len(objectsLookedIn)):
            objectId = objectsLookedIn[i]
            translationInfos = lazyResults[i]()
            text = translationInfos[0]
            score = 0.0
            if not text:
                continue

            rOne = regexpOne.findall(text)
            if not rOne:
                continue
            rAll = regexpAll.findall(text)
            if not rAll or nbTerms == 1:
                score += 1.0*len(rOne)/nbTerms
            else:
                score += 1.3*len(rAll)+0.7*len(rOne)/nbTerms

            score /= len(text)/200.0
            scores[objectId] = scores.setdefault(objectId, 0) + score

        return scores.items()


articlesServer = ArticlesServer()


if __name__ == '__main__':
    articlesServer.launch(applicationName, applicationRole)

