#!/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.

from __future__ import nested_scopes

__doc__ = """Glasnost Upload Files Server"""

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


import fcntl
import cStringIO
import os
from stat import *
import sys
import mimetypes

# From Python Imaging Library.
try:
    import Image as PILImage
except ImportError:
    PILImage = None

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

import glasnost

from glasnost.common.UploadFilesCommon import *
import glasnost.common.faults as faults
import glasnost.common.tools_new as commonTools
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 getApplicationId


applicationName = 'UploadFilesServer'
applicationRole = 'uploadfiles'
dispatcher = None


class AdminUploadFiles(AdminServerMixin, AdminUploadFilesCommon):
    pass
register(AdminUploadFiles)


class UploadFile(ObjectServerMixin, UploadFileCommon):
    def acquireNonCore(self, objectDirectoryPath = None,
                       dataDirectoryPath = None, parentSlot = None):
        self.properties = []
        ObjectServerMixin.acquireNonCore(
            self, objectDirectoryPath = objectDirectoryPath,
            dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
        self.loadData()
        self.loadProperties()

    def loadData(self):
        virtualServerId = self.getServer().computeVirtualServerId(self.id)
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        uploadFilesDirectoryPath = os.path.join(
            virtualServer.dataDirectoryPath, self.getServer().applicationRole)
        serverHostNameAndPort, serverRole, localId = \
                commonTools.splitId(self.id)
        dataFilePath = os.path.join(uploadFilesDirectoryPath, localId)
        try:
            dataFile = open(dataFilePath, 'rb')
        except IOError:
            if self.__dict__.has_key('data'):
                del self.data
            if self.__dict__.has_key('size'):
                del self.size
        else:
            fcntl.lockf(dataFile, fcntl.LOCK_SH)
            self.data = dataFile.read()
            fcntl.lockf(dataFile, fcntl.LOCK_UN)
            dataFile.close()
            # XMLRPC doesn't implements the handling of long integers.
            self.size = int(os.stat(dataFilePath)[ST_SIZE])

    def loadProperties(self):
        self.properties = []
        self.values = {}

        def newProperty(name, kindName, label, value):
            property = commonTools.newThing('other', 'Property')
            property.name = name
            property.kind = commonTools.newThing('kind', kindName)
            property.kind.isTranslatable = 0
            property.kind.label = label
            self.values[name] = value
            self.properties = self.properties[:]

        if self.isType('image') and PILImage and self.data:
            uploadFileFile = cStringIO.StringIO(self.data)
            try:
                uploadFileObject = PILImage.open(uploadFileFile)
            except IOError:
                pass
            else:
                width, height = uploadFileObject.size
                newProperty('extraWidth', 'Integer', N_('Width'), width)
                newProperty('extraHeight', 'Integer', N_('Height'), height)

                if hasattr(uploadFileObject, '_getexif'):
                    exifData = uploadFileObject._getexif()
                    exifKeys = { # gotten experimentally from gthumb;
                                 # TODO: read libexif
                        271: ('make', N_('Make')),
                        272: ('model', N_('Model')),
                        306: ('datetime', N_('Date')),
                        }
                    for key, value in exifKeys.items():
                        if not exifData or not exifData.has_key(key):
                            continue
                        newProperty(value[0], 'String', value[1],
                                str(exifData[key]))


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

    def removeDataFile(self):
        virtualServerId = self.getServer().computeVirtualServerId(self.id)
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        uploadFilesDirectoryPath = os.path.join(
            virtualServer.dataDirectoryPath, self.getServer().applicationRole)
        serverHostNameAndPort, serverRole, localId = \
                commonTools.splitId(self.id)
        dataFilePath = os.path.join(uploadFilesDirectoryPath, localId)
        try:
            os.remove(dataFilePath)
        except OSError, error:
            # Ignore 'No such file or directory' error.
            if error.errno != 2:
                raise

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

    def saveData(self):
        if self.data is None:
            self.removeDataFile()
            return
        virtualServerId = self.getServer().computeVirtualServerId(self.id)
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        uploadFilesDirectoryPath = 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(uploadFilesDirectoryPath, os.F_OK):
            os.mkdir(uploadFilesDirectoryPath)
            os.chmod(uploadFilesDirectoryPath, 0750)
        serverHostNameAndPort, serverRole, localId = \
                commonTools.splitId(self.id)
        dataFilePath = os.path.join(uploadFilesDirectoryPath, localId)
        dataFile = open(dataFilePath, 'wb')
        os.chmod(dataFilePath, 0640)
        fcntl.lockf(dataFile, fcntl.LOCK_EX)
        dataFile.write(self.data)
        fcntl.lockf(dataFile, fcntl.LOCK_UN)
        dataFile.close()
        # XMLRPC doesn't implements the handling of long integers.
        self.size = int(os.stat(dataFilePath)[ST_SIZE])

    def saveNonCore(self, objectDirectoryPath = None, dataDirectoryPath = None,
                    parentSlot = None):
        ObjectServerMixin.saveNonCore(
            self, objectDirectoryPath = objectDirectoryPath,
            dataDirectoryPath = dataDirectoryPath, parentSlot = parentSlot)
        self.saveData()
register(UploadFile)
    

class UploadFilesServer(UploadFilesCommonMixin, ObjectsServer):
    def addObjectXmlRpc(self, objectImport):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = commonTools.importThing(objectImport)
        if not object.dataType:
            object.dataType = 'application/octet-stream'
        # FIXME: We should move this to a new kind for mime types.
        if object.dataFileName \
           and (object.dataType == 'application/octet-stream'):
            mimeType = mimetypes.guess_type(object.dataFileName)
            if mimeType[0] is not None:
                object.dataType = mimeType[0]
        if 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
        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)
            sourceDataFilePath = os.path.join(
                sourceObjectsDirectoryPath, localId)
            destinationDataFilePath = os.path.join(
                destinationObjectsDirectoryPath, localId)
            if not os.access(sourceDataFilePath, 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)
            sourceDataFile = open(sourceDataFilePath, 'rb')
            destinationDataFile = open(destinationDataFilePath, 'wb')
            os.chmod(destinationDataFilePath, 0640)
            destinationDataFile.write(sourceDataFile.read())
            sourceDataFile.close()
            destinationDataFile.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 getObjectThumbnailXmlRpc(self, objectId, width, height):
        object = self.getObjectCore(objectId)
        object.acquireNonCore()
        try:
            if object.data:
                if object.isType('image') and PILImage:
                    objectFile = cStringIO.StringIO(object.data)
                    try:
                        objectObject = PILImage.open(objectFile)
                    except IOError:
                        pass
                    else:
                        try:
                            objectObject.thumbnail((width, height))
                        except IOError:
                            raise faults.UnresizableImage()
                        thumbnailFile = cStringIO.StringIO()
                        objectObject.save(
                            thumbnailFile, objectObject.format)
                        object.data = thumbnailFile.getvalue()
                        object.width, object.height = objectObject.size
            result = object.exportToXmlRpc()
        finally:
            object.releaseNonCore()
        return 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 modifyObjectXmlRpc(self, objectImport):
        objectChanges = commonTools.importThing(objectImport)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectChanges.id)
        object.acquireNonCore()
        try:
            if not object.canBeModified():
                raise faults.ReadOnlyObject()
            if not self.canModifyObject(object.id) or not (
                    self.isAdmin()
                    or not objectChanges.hasSlotName('writersSet')
                    or getProxyForServerRole('identities'
                    ).setContainsUser(
                    objectChanges.getSlot('writersSet').getValue())):
                if not object.canBeModifiedByClient():
                    raise faults.UserAccessDenied()
            object.checkModifyIsPossible(objectChanges)
            oldData = object.data
            object.modify(objectChanges)
            # FIXME: We should move this to a new kind for mime types.
            if object.dataFileName and \
                        object.dataType == 'application/octet-stream':
                mimeType = mimetypes.guess_type(object.dataFileName)
                if mimeType[0] is not None:
                    object.dataType = mimeType[0]
            object.saveNonCore()
        finally:
            object.releaseNonCore()
        virtualServer.markObjectAsDirty(object)
        invalidateValue(object.id)
        return object.version

    def registerPublicMethods(self):
        ObjectsServer.registerPublicMethods(self)
        self.registerPublicMethod('getObjectThumbnail',
                                         self.getObjectThumbnailXmlRpc)

    def repairVirtualServer(self, virtualServer, version):
        changed = 0
        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:
            for id, object in virtualServer.objects.items():
                if type(object.dataType) == type(()):
                    changed = 1
                    del object.dataType
                if object.dataFileName and \
                            object.dataType == 'application/octet-stream':
                    mimeType = mimetypes.guess_type(object.dataFileName)
                    if mimeType[0] is not None:
                        changed = 1
                        object.dataType = mimeType[0]
        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 <= 1018000:
            admin = virtualServer.admin
            if admin.id is None:
                changed = 1
                admin.id = '%s/__admin__' % virtualServer.virtualServerId
        if version <= 1037000:
            # cleaning dateFileName
            for id, object in virtualServer.objects.items():
                filename = object.dataFileName
                if not filename:
                    continue
                if '\\' in filename:
                    object.dataFileName == filename[ filename.rindex('\\')+1: ]
                    changed = 1
                elif '/' in filename:
                    object.dataFileName == filename[ filename.rindex('/')+1: ]
                    changed = 1
        if version <= 1039000:
            # filling empty mime-types with application/octet-stream
            for id, object in virtualServer.objects.items():
                if not object.dataType:
                    object.dataType = 'application/octet-stream'
                    changed = 1
        if changed:
            virtualServer.markAllAsDirtyFIXME()


uploadFilesServer = UploadFilesServer()


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

