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

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


import difflib
import md5
import sys
import time

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

import glasnost

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

from glasnost.server.ObjectsServer import register, AdministrableServerMixin, \
        AdminServerWithoutWritersMixin, ObjectServerMixin, Server, \
        AdministrableVirtualServer
from glasnost.server.tools import *

from glasnost.proxy.CacheProxy import invalidateKeyStart
from glasnost.proxy.tools import getProxy


applicationName = 'TranslationsServer'
applicationRole = 'translations'
dispatcher = None


class AdminTranslations(AdminServerWithoutWritersMixin,
                        AdminTranslationsCommon):
    def __init__(self):
        AdminTranslationsCommon.__init__(self)
        self.translatorsSets = {}

    def modify(self, changes, givenSlotNames = None):
        AdminServerWithoutWritersMixin.modify(
            self, changes, givenSlotNames = givenSlotNames)
        if self.translatorsSets is None:
            self.translatorsSets = {}
register(AdminTranslations)

class Localization(LocalizationCommon):
    pass
register(Localization)


class Translation(ObjectServerMixin, ObjectCommon):
    creationTimes = None
    creationTimes_kind_keyKind_valueName = 'String'
    creationTimes_kind_valueKind_valueName = 'Time'
    creationTimes_kindName = 'Mapping'

    destinationStrings = None
    destinationStrings_kind_keyKind_valueName = 'String'
    destinationStrings_kind_valueKind_valueName = 'String'
    destinationStrings_kindName = 'Mapping'

    fuzzyLocalizations = None
    fuzzyLocalizations_kind_itemKind_valueName = 'String'
    fuzzyLocalizations_kindName = 'Sequence'

    previousVersionSigns = None
    previousVersionSigns_kind_keyKind_valueName = 'Source'
    previousVersionSigns_kind_valueKind_valueName = 'String'
    previousVersionSigns_kindName = 'Mapping'

    sourceLanguage = None
    sourceLanguage_kindName = 'Choice'

    sources = None
    sources_kind_itemKind_valueName = 'Source'
    sources_kindName = 'Sequence'

    sourceString = None
    sourceString_kindName = 'String'
    
    sourceStringDigest = None
    sourceStringDigest_kindName = 'String'

    translatorIds = None
    translatorIds_kind_keyKind_valueName = 'String'
    translatorIds_kind_valueKind_valueName = 'Sequence'
    translatorIds_kind_valueKind_value_itemKind_valueName = 'Id'
    translatorIds_kindName = 'Mapping'

    untranslatableLocalizations = None
    untranslatableLocalizations_kind_itemKind_valueName = 'String'
    untranslatableLocalizations_kindName = 'Sequence'
            
    def addTranslator(self, destinationLanguage, userId):
        if not self.translatorIds:
            self.translatorIds = {}
        if self.translatorIds.has_key(destinationLanguage):
            if userId in self.translatorIds[destinationLanguage]:
                pass
            else:
                self.translatorIds[destinationLanguage].append(userId)
        else:
            self.translatorIds[destinationLanguage] = [userId]

    def checkLocalizationState(self, destinationLanguage, possibleStates):
        if not possibleStates:
            return 1
        if not self.sources:
            state = 'obsolete'
        elif destinationLanguage == self.sourceLanguage:
            state = 'original'
        elif self.untranslatableLocalizations is not None \
             and destinationLanguage in self.untranslatableLocalizations:
            state = 'untranslatable'
        elif not self.hasDestinationString(destinationLanguage):
            state = 'untranslated'
        elif self.fuzzyLocalizations is not None \
             and destinationLanguage in self.fuzzyLocalizations:
            state = 'fuzzy'
        else:
            state = 'translated'
        return state in possibleStates

    def getDestinationString(self, destinationLanguage):
        if destinationLanguage == self.sourceLanguage:
            return self.sourceString
        elif self.destinationStrings is not None \
           and self.destinationStrings.has_key(destinationLanguage):
            return self.destinationStrings[destinationLanguage]
        else:
            return None

    def getTranslatorsSet(self, destinationLanguage):
        if not self.translatorIds or \
                not self.translatorIds.has_key(destinationLanguage):
            return []
        return self.translatorIds[destinationLanguage]

    def getLabel(self):
        label = self.getTitle()
        if label is None:
            return ''
        return label

    def getTitle(self):
        title = self.sourceString
        if len(title) > 80:
            title = title[:60] + '...'
        return title

    def hasDestinationString(self, destinationLanguage):
        return destinationLanguage == self.sourceLanguage \
               or self.destinationStrings is not None \
               and self.destinationStrings.has_key(destinationLanguage)

    def isPreviousTranslation(self, translation, visitedTranslations):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        if self.previousVersionSigns is None:
            return 0
        for previousVersionSign in self.previousVersionSigns.values():
            if not virtualServer.translations.has_key(previousVersionSign):
                continue
            previousTranslation = virtualServer.translations[
                previousVersionSign]
            if previousTranslation in visitedTranslations:
                return 0
            if previousTranslation == translation:
                return 1
            visitedTranslations.append(previousTranslation)
            if previousTranslation.isPreviousTranslation(
                    translation, visitedTranslations):
                return 1
        return 0

    def setDestinationString(self, destinationLanguage, isTranslatable,
                destinationString, isFuzzy):
        if isTranslatable:
            if self.untranslatableLocalizations is not None and \
                    destinationLanguage in self.untranslatableLocalizations:
                self.untranslatableLocalizations.remove(destinationLanguage)
                if not self.untranslatableLocalizations:
                    del self.untranslatableLocalizations
        else:
            if self.untranslatableLocalizations is None:
                self.untranslatableLocalizations = []
            if not destinationLanguage in self.untranslatableLocalizations:
                self.untranslatableLocalizations.append(destinationLanguage)
            destinationString = None
            isFuzzy = 0
        if not destinationString:
            if self.destinationStrings is not None and \
                    self.destinationStrings.has_key(destinationLanguage):
                del self.destinationStrings[destinationLanguage]
                if not self.destinationStrings:
                    del self.destinationStrings
            if self.fuzzyLocalizations is not None and \
                    destinationLanguage in self.fuzzyLocalizations:
                self.fuzzyLocalizations.remove(destinationLanguage)
                if not self.fuzzyLocalizations:
                    del self.fuzzyLocalizations
        else:
            if self.destinationStrings is None:
                self.destinationStrings = {}
            if self.creationTimes is None:
                self.creationTimes = {}
            self.destinationStrings[destinationLanguage] = destinationString
            self.creationTimes[destinationLanguage] = time.time()
            if isFuzzy:
                if self.fuzzyLocalizations is None:
                    self.fuzzyLocalizations = []
                if not destinationLanguage in self.fuzzyLocalizations:
                    self.fuzzyLocalizations.append(destinationLanguage)
            elif self.fuzzyLocalizations is not None and \
                    destinationLanguage in self.fuzzyLocalizations:
                self.fuzzyLocalizations.remove(destinationLanguage)
                if not self.fuzzyLocalizations:
                    del self.fuzzyLocalizations
register(Translation)


class TranslationsVirtualServer(AdministrableVirtualServer):
    translations = None
    translationsBySource = None

    def convertIds(self, sourceDispatcherId, destinationDispatcherId):
        AdministrableVirtualServer.convertIds(
                self, sourceDispatcherId, destinationDispatcherId)
        for sign, translation in self.translations.items():
            translation.convertIds(sourceDispatcherId, destinationDispatcherId)
        for source, translation in self.translationsBySource.items():
            newSource = source.replace(
                sourceDispatcherId, destinationDispatcherId)
            if newSource != source:
                del self.translationsBySource[source]
                self.translationsBySource[newSource] = translation
        
    def init(self):
        AdministrableVirtualServer.init(self)
        self.translations = {}
        self.translationsBySource = {}

    def initFromOldData(self, data):
        AdministrableVirtualServer.initFromOldData(self, data)
        (self.translations,
         self.translationsBySource,
         self.admin,
         ) = data

    def removeIds(self, rolesToKeep):
        import glasnost.common.tools_new as commonTools
        AdministrableVirtualServer.removeIds(self, rolesToKeep)
        for sign, translation in self.translations.items():
            translation.removeIds(rolesToKeep)
        for source, translation in self.translationsBySource.items():
            if not commonTools.extractRole(source) in rolesToKeep:
                del self.translationsBySource[source]


class TranslationsServer(TranslationsCommonMixin, AdministrableServerMixin,
                         Server):
    VirtualServer = TranslationsVirtualServer
    useAdminReadersSet = 1

    def canDeleteLocalization(self, localizationKey, sourceStringDigest):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if virtualServer.admin.translatorsSets.has_key(localizationKey):
            translatorsSet = virtualServer.admin.translatorsSets[
                localizationKey]
        else:
            translatorsSet = None
        result = self.isAdmin() \
                or getProxyForServerRole('identities').setContainsUser(
                       translatorsSet)
        return result

    def canModifyLocalization(self, localizationKey, sourceStringDigest):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.admin.translatorsSets.has_key(localizationKey):
            return 0
        translatorsSet = virtualServer.admin.translatorsSets[localizationKey]
        identitiesProxy = getProxyForServerRole('identities')
        return self.isAdmin() or \
                identitiesProxy.setContainsUser(translatorsSet)

    def getLanguagesForObjectId(self, objectId):
        #languages = self.getPossibleLanguages()
        languages = translation.languageKeys
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        t = objectId + ' '
        translations = [
                virtualServer.translationsBySource[k]
                for k in virtualServer.translationsBySource.keys()
                if k.startswith(t)]
        l = len(translations)
        result = []
        for lang in languages:
            if len( [x for x in translations \
                     if x.sourceLanguage == lang] ) == l:
                result.append(lang)
                continue
            if len([x for x in translations
                    if (x.destinationStrings and
                         x.destinationStrings.has_key(lang)) or (
                         lang in (x.untranslatableLocalizations or []))]) == l:
                if not lang in (x.fuzzyLocalizations or []):
                    result.append(lang)
                continue

        return result

    def getSomeDigestsAndLabelsXmlRpc(
           self, localizationKey, digestsAndLabelsCount, possibleStates):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if virtualServer.admin.translatorsSets.has_key(localizationKey):
            translatorsSet = virtualServer.admin.translatorsSets[
                localizationKey]
        else:
            translatorsSet = None
        if not self.isAdmin() \
               and not getProxyForServerRole('identities'
                       ).setContainsUser(translatorsSet):
            raise faults.UserAccessDenied()
        digestsAndLabels = []
        for sign, translation in virtualServer.translations.items():
            if not sign.startswith(localizationKey[:2]):
                continue
            if not translation.checkLocalizationState(
                localizationKey[2:], possibleStates):
                continue
            digestsAndLabels.append(
                [translation.sourceStringDigest,
                 utf8(translation.getLabel()),
                 len(translation.sourceString.split())])
            if digestsAndLabelsCount == -1:
                continue
            if len(digestsAndLabels) >= digestsAndLabelsCount:
                break
        return digestsAndLabels

    def getLocalizationXmlRpc(self, localizationKey, sourceStringDigest):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        sign = localizationKey[:2] + sourceStringDigest
        if not virtualServer.translations.has_key(sign):
            raise faults.MissingItem(sign)
        translation = virtualServer.translations[sign]
        if virtualServer.admin.translatorsSets.has_key(localizationKey):
            translatorsSet = virtualServer.admin.translatorsSets[
                localizationKey]
        else:
            translatorsSet = None
        if not self.isAdmin() \
               and not getProxyForServerRole('identities'
                       ).setContainsUser(translatorsSet):
            raise faults.UserAccessDenied()
        destinationLanguage = localizationKey[2:]
        localization = Localization()
        localization.version = translation.version
        localization.destinationLanguage = destinationLanguage
        if translation.sources:
            sourceIds = [ source.split(' ', 1)[0]
                          for source in translation.sources]
            localization.sourceIds = []
            for sourceId in sourceIds:
                if not sourceId in localization.sourceIds:
                    localization.sourceIds.append(sourceId)
        localization.sourceLanguage = translation.sourceLanguage
        localization.sourceString = translation.sourceString
        localization.sourceStringDigest = translation.sourceStringDigest
        localization.isTranslatable = (
            translation.untranslatableLocalizations is None
            or not destinationLanguage in \
            translation.untranslatableLocalizations)

        # not translatable
        if not localization.isTranslatable:
            return localization.exportToXmlRpc()
        
        # already translated
        if translation.hasDestinationString(destinationLanguage):
            localization.translatorsSet = \
                    translation.getTranslatorsSet(destinationLanguage)
            localization.destinationString = \
                    translation.getDestinationString(destinationLanguage)
            localization.isFuzzy = (
                translation.fuzzyLocalizations is not None
                and destinationLanguage in translation.fuzzyLocalizations)
            return localization.exportToXmlRpc()

        # not translated
        if translation.sources:
            possibleTranslations = []
            for source in translation.sources:
                for trans in virtualServer.translations.values():
                    if source in trans.sources:
                        possibleTranslations.append(trans)
                    if not trans.previousVersionSigns:
                        continue
                    for src, sign in trans.previousVersionSigns.items():
                        if not source == src:
                            continue
                        possibleTranslations.append(
                                virtualServer.translations[sign])
            
            oldResults = []
            for trans in possibleTranslations:
                if not trans.getDestinationString(destinationLanguage):
                    continue
                if not trans.creationTimes or \
                        not trans.creationTimes.has_key(destinationLanguage):
                    oldResults.append( (0, trans) )
                else:
                    oldResults.append(
                            (trans.creationTimes[destinationLanguage],
                            trans) )
            oldResults.sort()
            if oldResults:
                # it once was translated; we return the last known translation
                # for the same sourcePath
                translation = oldResults[-1][1]
                localization.isFuzzy = 1
                localization.similarString = translation.sourceString
                if translation.destinationStrings and \
                        translation.destinationStrings.has_key(
                            destinationLanguage):
                    localization.destinationString = \
                            translation.destinationStrings[destinationLanguage]
                    return localization.exportToXmlRpc()

        # we try to return a translation matching what's asked
        sequenceMatcher = difflib.SequenceMatcher(None)
        sequenceMatcher.set_seq1(localization.sourceString)
        bestRatio = 0.8
        for similarSign, similarTranslation in \
            virtualServer.translations.items():
            if not similarSign.startswith(localizationKey[:2]):
                continue
            if similarTranslation == translation:
                continue
            if similarTranslation.destinationStrings is None or \
               not similarTranslation.destinationStrings.has_key(
                destinationLanguage):
                continue
            if similarTranslation.fuzzyLocalizations is not None \
               and destinationLanguage in \
               similarTranslation.fuzzyLocalizations:
                continue
            sequenceMatcher.set_seq2(similarTranslation.sourceString)
            ratio = sequenceMatcher.quick_ratio()
            if ratio <= bestRatio:
                continue
            if not translation.isPreviousTranslation(
                    similarTranslation, []):
                for source in similarTranslation.sources:
                    id, path = source.split(' ', 1)
                    try:
                        if id.endswith('/__admin__'):
                            getProxy(id).getAdminStringFromDigest(
                                path, similarTranslation.sourceStringDigest)
                        else:
                            getProxy(id).getObjectStringFromDigest(
                                id, path,
                                similarTranslation.sourceStringDigest)
                    except faults.Fault, fault:
                        pass
                    else:
                        # The translator has read access to the similar
                        # string in the current source.
                        break
                else:
                    # The translator has no read access to the similar
                    # string.
                    continue
            bestRatio = ratio
            localization.similarString = \
                                       similarTranslation.sourceString
            localization.destinationString = \
                similarTranslation.destinationStrings[
                destinationLanguage]
            localization.isFuzzy = 1
        return localization.exportToXmlRpc()

    def getPossibleLanguages(self):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        translatorsSets = virtualServer.admin.translatorsSets
        if not translatorsSets:
            return []
        languages = {}
        for k in translatorsSets.keys():
            languages[k[:2]] = None
            languages[k[2:]] = None
        return languages.keys()

    def getTranslationInfos(
            self, sourceStringDigest, sourceId, sourcePath, sourceLanguage,
            destinationLanguages, ignoreNew = 0):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if commonTools.extractRole(sourceId) == 'virtualhosts':
            try:
                virtualHost = getProxy(sourceId).getObject(sourceId)
            except (faults.MissingItem, faults.UserAccessDenied,
                    faults.UnknownDispatcherInId):
                return ('', '', 'ignored')
            fakeSourceId = virtualHost.defaultDispatcherId + '/foo/bar'
            virtualServerId = self.computeVirtualServerId(fakeSourceId)
            try:
                virtualServer = self.getVirtualServer(virtualServerId)
            except KeyError:
                # The virtual host is not running or doesn't exist anymore.
                return ('', '', 'ignored')
        source = sourceId + ' ' + sourcePath
        sign = sourceLanguage + sourceStringDigest
        if virtualServer.translations.has_key(sign):
            translation = virtualServer.translations[sign]
            if not source in translation.sources and not ignoreNew:
                # Check that the string exists on the server before creating
                # its translation, to avoid the creation of fake translations
                # by any user.
                # If getObjectStringFromDigest() raises an exception, it is
                # propagated to the caller of getTranslation().
                proxy = getProxy(sourceId)
                if sourceId.endswith('/__admin__'):
                    sourceString = proxy.getAdminStringFromDigest(
                            sourcePath, sourceStringDigest)
                else:
                    sourceString = proxy.getObjectStringFromDigest(
                        sourceId, sourcePath, sourceStringDigest)
                virtualServer.lock.acquire()
                translation.sources.append(source)
                if virtualServer.translationsBySource.has_key(source):
                    previousTranslation = virtualServer.translationsBySource[
                        source]
                    previousTranslation.sources.remove(source)
                    if previousTranslation.sourceLanguage == sourceLanguage:
                        if translation.previousVersionSigns is None:
                            translation.previousVersionSigns = {}
                        translation.previousVersionSigns[source] = \
                            sourceLanguage \
                            + previousTranslation.sourceStringDigest
                virtualServer.translationsBySource[source] = translation
                virtualServer.lock.release()
                virtualServer.markCoreAsDirty()
        elif not ignoreNew:
            if virtualServerId in ['glasnost://help', 'glasnost://system']:
                return ('', '', 'ignored') # or use gettext ?

            # Check that the string exists on the server before creating its
            # translation, to avoid the creation of fake translations by any
            # user.
            # If getObjectStringFromDigest() raises an exception, it is
            # propagated to the caller of getTranslation().
            proxy = getProxy(sourceId)
            if sourceId.endswith('/__admin__'):
                sourceString = proxy.getAdminStringFromDigest(
                        sourcePath, sourceStringDigest)
            else:
                sourceString = proxy.getObjectStringFromDigest(
                    sourceId, sourcePath, sourceStringDigest)
            translation = Translation()
            translation.sourceLanguage = sourceLanguage
            translation.sourceString = sourceString
            translation.sourceStringDigest = sourceStringDigest
            translation.sources = [source]
            virtualServer.lock.acquire()
            virtualServer.translations[sign] = translation
            if virtualServer.translationsBySource.has_key(source):
                previousTranslation = virtualServer.translationsBySource[
                    source]
                previousTranslation.sources.remove(source)
                if previousTranslation.sourceLanguage == sourceLanguage:
                    translation.previousVersionSigns = {
                        source: sourceLanguage \
                        + previousTranslation.sourceStringDigest,
                        }
            virtualServer.translationsBySource[source] = translation
            virtualServer.lock.release()
            virtualServer.markCoreAsDirty()
        else:
            return ('', '', 'ignored')
 
        state = 'invariant'
        if destinationLanguages:
            # Look for an up to date translation.
            destinationLanguage = destinationLanguages[0]
            if destinationLanguage == sourceLanguage:
                return translation.sourceString, sourceLanguage, 'original'
            if translation.untranslatableLocalizations is not None and \
               destinationLanguage in translation.untranslatableLocalizations:
                return (translation.sourceString,
                        sourceLanguage,
                        'untranslatable')
            if translation.fuzzyLocalizations is not None and \
               destinationLanguage in translation.fuzzyLocalizations:
                state = 'fuzzy'
            else:
                destinationString = translation.getDestinationString(
                    destinationLanguage)
                if destinationString is not None:
                    return destinationString, destinationLanguage, 'translated'
                state = 'untranslated'
            # Look for an obsolete translation.
            currentTranslation = translation
            visitedTranslations = []
            while currentTranslation.previousVersionSigns is not None and \
               currentTranslation.previousVersionSigns.has_key(source):
                previousVersionSign = currentTranslation.previousVersionSigns[
                    source]
                if not virtualServer.translations.has_key(previousVersionSign):
                    break
                visitedTranslations.append(currentTranslation)
                currentTranslation = virtualServer.translations[
                    previousVersionSign]
                if currentTranslation in visitedTranslations:
                    # Vicious circle in the translation history.
                    break
                # We make the assumption that the reader has the right to read
                # the previous version of the sourceString.
                if currentTranslation.untranslatableLocalizations is not None \
                   and destinationLanguage in \
                   currentTranslation.untranslatableLocalizations:
                    return currentTranslation.sourceString, sourceLanguage, \
                           'obsolete'
                if currentTranslation.fuzzyLocalizations is not None \
                   and destinationLanguage in \
                   currentTranslation.fuzzyLocalizations:
                    continue
                destinationString = currentTranslation.getDestinationString(
                    destinationLanguage)
                if destinationString is None:
                    continue
                return destinationString, destinationLanguage, 'obsolete'
            # Look for a translation in another language.
            for destinationLanguage in destinationLanguages[1:]:
                if destinationLanguage == sourceLanguage:
                    return translation.sourceString, sourceLanguage, state
                if translation.untranslatableLocalizations is not None \
                   and destinationLanguage in \
                   translation.untranslatableLocalizations:
                    return translation.sourceString, sourceLanguage, state
                if translation.fuzzyLocalizations is not None and \
                   destinationLanguage in translation.fuzzyLocalizations:
                    continue
                destinationString = translation.getDestinationString(
                    destinationLanguage)
                if destinationString is None:
                    continue
                return destinationString, destinationLanguage, state
        return translation.sourceString, sourceLanguage, state

    def getTranslationInfosXmlRpc(
            self, sourceStringDigest, sourceId, sourcePath, sourceLanguage,
            destinationLanguages, ignoreNew):
        infos = list(self.getTranslationInfos(
                sourceStringDigest, sourceId, sourcePath, sourceLanguage,
                destinationLanguages, ignoreNew))
        infos[0] = utf8(infos[0])
        return infos

    def getTranslationXmlRpc(
            self, sourceStringDigest, sourceId, sourcePath, sourceLanguage,
            destinationLanguages):
        result = utf8(self.getTranslationInfos(
                sourceStringDigest, sourceId, sourcePath, sourceLanguage,
                destinationLanguages)[0])
        return result

    def getTranslatorLocalizationKeys(self):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        userToken = context.getVar('userToken')
        if not userToken:
            return []
        localizationKeys = []
        for localizationKey, translatorsSet in \
            virtualServer.admin.translatorsSets.items():
            if getProxyForServerRole('identities').setContainsUser(
                    translatorsSet):
                localizationKeys.append(localizationKey)
        return localizationKeys

    def hasLocalization(self, localizationKey, sourceStringDigest):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        sign = localizationKey[:2] + sourceStringDigest
        if not virtualServer.translations.has_key(sign):
            return 0
        translation = virtualServer.translations[sign]
        return translation.hasDestinationString(localizationKey[2:])

    def hasSourceString(self, localizationKey, sourceStringDigest):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        sign = localizationKey[:2] + sourceStringDigest
        return virtualServer.translations.has_key(sign)

    def modifyLocalizationXmlRpc(self, localizationImport):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        localization = commonTools.importThing(localizationImport)
        sign = localization.sourceLanguage \
               + localization.sourceStringDigest
        if not virtualServer.translations.has_key(sign):
            raise faults.MissingItem(sign)
        translation = virtualServer.translations[sign]
        if not translation.canBeModified():
            raise faults.ReadOnlyObject()
        if virtualServer.admin.translatorsSets.has_key(
            localization.getKey()):
            translatorsSet = virtualServer.admin.translatorsSets[
                localization.getKey()]
        else:
            translatorsSet = None
        if not self.isAdmin() \
               and not getProxyForServerRole('identities'
                       ).setContainsUser(translatorsSet):
            raise faults.UserAccessDenied()
        assert translation.sourceLanguage == localization.sourceLanguage
        virtualServer.lock.acquire()
        # Check once again, now that the thread is locked.
        if not virtualServer.translations.has_key(sign):
            virtualServer.lock.release()
            raise faults.MissingItem(sign)
        translation.version += 1
        translation.setDestinationString(
            localization.destinationLanguage, localization.isTranslatable,
            localization.destinationString, localization.isFuzzy)
        userId = getProxyForServerRole('identities').getUserId()
        translation.addTranslator(localization.destinationLanguage, userId)
        if not translation.destinationStrings and not translation.sources:
            del virtualServer.translations[sign]
        virtualServer.lock.release()
        virtualServer.markCoreAsDirty()

        cacheKey = '_' .join([localization.sourceStringDigest,
                              localization.sourceLanguage,
                              ''])
        invalidateKeyStart(cacheKey)
        return translation.version

    def registerPublicMethods(self):
        Server.registerPublicMethods(self)
        AdministrableServerMixin.registerPublicMethods(self)
        self.registerPublicMethod('canDeleteLocalization')
        self.registerPublicMethod('canModifyLocalization')
        self.registerPublicMethod('getLanguagesForObjectId')
        self.registerPublicMethod('getSomeDigestsAndLabels',
                                         self.getSomeDigestsAndLabelsXmlRpc)
        self.registerPublicMethod('getPossibleLanguages',
                                         self.getPossibleLanguages)
        self.registerPublicMethod('getLocalization',
                                         self.getLocalizationXmlRpc)
        self.registerPublicMethod('getTranslation',
                                         self.getTranslationXmlRpc)
        self.registerPublicMethod('getTranslationInfos',
                                         self.getTranslationInfosXmlRpc)
        self.registerPublicMethod('getTranslatorLocalizationKeys')
        self.registerPublicMethod('hasLocalization')
        self.registerPublicMethod('hasSourceString')
        self.registerPublicMethod('modifyLocalization',
                                         self.modifyLocalizationXmlRpc)

    def repairVirtualServer(self, virtualServer, version):
        changed = 0
        if version < 4000:
            changed = virtualServer.admin.repair(4000) or changed
            for sign, translation in virtualServer.translations.items():
                changed = translation.repair(4000) or changed
        if version < 5004:
            changed = virtualServer.admin.repair(5004) or changed
            for sign, translation in virtualServer.translations.items():
                changed = translation.repair(5004) or changed
        if version <= 1012000:
            admin = virtualServer.admin
            if admin.id is None:
                changed = 1
                admin.id = '%s/__admin__' % virtualServer.virtualServerId
        if changed:
            virtualServer.markAllAsDirtyFIXME()

    def upgradeVirtualServer_0001_0028(self, virtualServer):
        AdministrableServerMixin.upgradeVirtualServer_0001_0028(
                self, virtualServer)
        
        for translation in virtualServer.translations.values():
            if translation.upgrade('0001_0028'):
                virtualServer.markAllAsDirtyFIXME()

translationsServer = TranslationsServer()


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

