492 lines
21 KiB
Python
Executable File
492 lines
21 KiB
Python
Executable File
#!/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 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):
|
|
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.append(property)
|
|
|
|
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.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().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:
|
|
objectObject.thumbnail((width, height))
|
|
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)
|
|
|