Commit dda491be authored by Nicolas Delaby's avatar Nicolas Delaby

Follow cached_convertable interface by adding following

public methods:
  - getConversion
  - getConversionMd5
  - getConversionDate
  - getConversionSize




git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@35216 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 70c46ac0
...@@ -35,12 +35,29 @@ from AccessControl import ClassSecurityInfo ...@@ -35,12 +35,29 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from OFS.Image import Pdata, Image as OFSImage
from DateTime import DateTime
def makeSortedTuple(kw): def makeSortedTuple(kw):
items = kw.items() items = kw.items()
items.sort() items.sort()
return tuple(items) return tuple(items)
def hashPdataObject(data):
"""Pdata objects are iterable, use this feature strongly
to minimize memory footprint.
"""
md5_hash = md5.new()
next = chunk = data.next
if next is None:
md5_hash.update(data.data)
while next is not None:
chunk = next
md5_hash.update(chunk)
next = data.next
return md5_hash.hexdigest()
class CachedConvertableMixin: class CachedConvertableMixin:
""" """
This class provides a generic implementation of IConvertable. This class provides a generic implementation of IConvertable.
...@@ -78,7 +95,7 @@ class CachedConvertableMixin: ...@@ -78,7 +95,7 @@ class CachedConvertableMixin:
cache_tool.updateCache() cache_tool.updateCache()
return cache_tool.getRamCacheRoot().get(cache_factory_name) return cache_tool.getRamCacheRoot().get(cache_factory_name)
def _getCacheKey(self): def _getCacheKey(self, **kw):
""" """
Returns the key to use for the cache entries. For now, Returns the key to use for the cache entries. For now,
use the object uid. use the object uid.
...@@ -87,27 +104,14 @@ class CachedConvertableMixin: ...@@ -87,27 +104,14 @@ class CachedConvertableMixin:
http://pypi.python.org/pypi/uuid/ to generate http://pypi.python.org/pypi/uuid/ to generate
a uuid stored as private property. a uuid stored as private property.
""" """
return '%s:%s' % (aq_base(self).getUid(), self.getRevision()) format_cache_id = str(makeSortedTuple(kw)).\
translate(string.maketrans('', ''), '[]()<>\'", ')
return '%s:%s:%s' % (aq_base(self).getUid(), self.getRevision(),
format_cache_id)
security.declareProtected(Permissions.View, 'hasConversion') security.declareProtected(Permissions.View, 'hasConversion')
def hasConversion(self, **kw): def hasConversion(self, **kw):
""" """
If you want to get conversion cache value if exists, please write
the code like:
try:
mime, data = getConversion(**kw)
except KeyError:
...
instead of:
if self.hasConversion(**kw):
mime, data = self.getConversion(**kw)
else:
...
for better performance.
""" """
try: try:
self.getConversion(**kw) self.getConversion(**kw)
...@@ -116,70 +120,115 @@ class CachedConvertableMixin: ...@@ -116,70 +120,115 @@ class CachedConvertableMixin:
return False return False
security.declareProtected(Permissions.ModifyPortalContent, 'setConversion') security.declareProtected(Permissions.ModifyPortalContent, 'setConversion')
def setConversion(self, data, mime=None, calculation_time=None, **kw): def setConversion(self, data, mime=None, date=None, **kw):
""" """
""" """
cache_id = '%s%s' % (self._getCacheKey(), self.generateCacheId(**kw)) cache_id = self._getCacheKey(**kw)
if data is None:
cached_value = None
conversion_md5 = None
size = 0
elif isinstance(data, Pdata):
cached_value = aq_base(data)
conversion_md5 = hashPdataObject(cached_value)
size = len(cached_value)
elif isinstance(data, OFSImage):
cached_value = data
conversion_md5 = md5.new(str(data.data)).hexdigest()
size = len(data.data)
else:
cached_value = data
conversion_md5 = md5.new(cached_value).hexdigest()
size = len(cached_value)
if date is None:
date = DateTime()
stored_data_dict = {'content_md5': self.getContentMd5(),
'conversion_md5': conversion_md5,
'mime': mime,
'data': cached_value,
'date': date,
'size': size}
if self.isTempObject(): if self.isTempObject():
if getattr(aq_base(self), 'temp_conversion_data', None) is None: if getattr(aq_base(self), 'temp_conversion_data', None) is None:
self.temp_conversion_data = {} self.temp_conversion_data = {}
self.temp_conversion_data[cache_id] = (mime, aq_base(data)) self.temp_conversion_data[cache_id] = stored_data_dict
return return
cache_factory = self._getCacheFactory() cache_factory = self._getCacheFactory()
cache_duration = cache_factory.cache_duration cache_duration = cache_factory.cache_duration
if data is not None: # The purpose of this transaction cache is to help calls
# to the same cache value in the same transaction.
tv = getTransactionalVariable(None)
tv[cache_id] = stored_data_dict
for cache_plugin in cache_factory.getCachePluginList(): for cache_plugin in cache_factory.getCachePluginList():
cache_plugin.set(cache_id, DEFAULT_CACHE_SCOPE, cache_plugin.set(cache_id, DEFAULT_CACHE_SCOPE,
(self.getContentMd5(), mime, aq_base(data)), stored_data_dict, cache_duration=cache_duration)
calculation_time=calculation_time,
cache_duration=cache_duration)
security.declareProtected(Permissions.View, 'getConversion') security.declareProtected(Permissions.View, '_getConversionDataDict')
def getConversion(self, **kw): def _getConversionDataDict(self, **kw):
""" """
""" """
cache_id = '%s%s' % (self._getCacheKey(), self.generateCacheId(**kw)) cache_id = self._getCacheKey(**kw)
if self.isTempObject(): if self.isTempObject():
return getattr(aq_base(self), 'temp_conversion_data', {})[cache_id] return getattr(aq_base(self), 'temp_conversion_data', {})[cache_id]
# The purpose of this cache is to help calls to the same cache value
# in the same transaction.
tv = getTransactionalVariable(None)
try:
return tv[cache_id]
except KeyError:
pass
for cache_plugin in self._getCacheFactory().getCachePluginList(): for cache_plugin in self._getCacheFactory().getCachePluginList():
cache_entry = cache_plugin.get(cache_id, DEFAULT_CACHE_SCOPE) cache_entry = cache_plugin.get(cache_id, DEFAULT_CACHE_SCOPE)
if cache_entry is not None: if cache_entry is not None:
data_list = cache_entry.getValue() data_dict = cache_entry.getValue()
if data_list: if data_dict:
md5sum, mime, data = data_list content_md5 = data_dict['content_md5']
if md5sum != self.getContentMd5(): if content_md5 != self.getContentMd5():
raise KeyError, 'Conversion cache key is compromised for %r' % cache_id raise KeyError, 'Conversion cache key is compromised for %r' % cache_id
return mime, data # Fill transactional cache in order to help
# querying real cache during same transaction
tv[cache_id] = data_dict
return data_dict
raise KeyError, 'Conversion cache key does not exists for %r' % cache_id raise KeyError, 'Conversion cache key does not exists for %r' % cache_id
security.declareProtected(Permissions.View, 'getConversion')
def getConversion(self, **kw):
"""
"""
cached_dict = self._getConversionDataDict(**kw)
return cached_dict['mime'], cached_dict['data']
security.declareProtected(Permissions.View, 'getConversionSize') security.declareProtected(Permissions.View, 'getConversionSize')
def getConversionSize(self, **kw): def getConversionSize(self, **kw):
""" """
""" """
try: try:
mime, data = self.getConversion(**kw) return self._getConversionDataDict(**kw)['size']
return len(data)
except KeyError: except KeyError:
# If conversion doesn't exists return 0
return 0 return 0
def generateCacheId(self, **kw): security.declareProtected(Permissions.View, 'getConversionDate')
"""Generate proper cache id based on **kw. def getConversionDate(self, **kw):
Function inspired from ERP5Type.Cache """
""" """
return str(makeSortedTuple(kw)).translate(string.maketrans('', ''), '[]()<>\'", ') return self._getConversionDataDict(**kw)['date']
security.declareProtected(Permissions.View, 'getConversionMd5')
def getConversionMd5(self, **kw):
"""
"""
return self._getConversionDataDict(**kw)['conversion_md5']
security.declareProtected(Permissions.ModifyPortalContent, 'updateContentMd5') security.declareProtected(Permissions.ModifyPortalContent, 'updateContentMd5')
def updateContentMd5(self): def updateContentMd5(self):
"""Update md5 checksum from the original file """Update md5 checksum from the original file
XXX-JPS - this method is not part of any interfacce.
should it be public or private. It is called
by some interaction workflow already. Is
it general or related to caching only ?
""" """
data = self.getData() mime, data = self.convert(None)
if data is not None: if data is not None:
data = str(data) # Usefull for Pdata if isinstance(data, Pdata):
self._setContentMd5(hashPdataObject(aq_base(data)))
else:
self._setContentMd5(md5.new(data).hexdigest()) # Reindex is useless self._setContentMd5(md5.new(data).hexdigest()) # Reindex is useless
else: else:
self._setContentMd5(None) self._setContentMd5(None)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment