# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. # Jean-Paul Smets-Solanes <jp@nexedi.com> # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contract a Free Software # Service Company # # 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. # ############################################################################## import md5 import string from Acquisition import aq_base from AccessControl import ClassSecurityInfo from Products.ERP5Type import Permissions from Products.CMFCore.utils import getToolByName from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE def makeSortedTuple(kw): items = kw.items() items.sort() return tuple(items) class CachedConvertableMixin: """ This class provides a generic implementation of IConvertable. This class provides a generic API to store using portal_caches plugin structure various converted versions of a file or of a string. Versions are stored in dictionaries; the class stores also generation time of every format and its mime-type string. Format can be a string or a tuple (e.g. format, resolution). """ # Declarative security security = ClassSecurityInfo() # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) def _getCacheFactory(self): """ """ if self.isTempObject(): return cache_tool = getToolByName(self, 'portal_caches') preference_tool = getToolByName(self, 'portal_preferences') cache_factory_name = preference_tool.getPreferredConversionCacheFactory('document_cache_factory') cache_factory = cache_tool.getRamCacheRoot().get(cache_factory_name) #XXX This conditional statement should be remove as soon as #Broadcasting will be enable among all zeo clients. #Interaction which update portal_caches should interact with all nodes. if cache_factory is None and getattr(cache_tool, cache_factory_name, None) is not None: #ram_cache_root is not up to date for current node cache_tool.updateCache() return cache_tool.getRamCacheRoot().get(cache_factory_name) def _getCacheKey(self): """ Returns the key to use for the cache entries. For now, use the object uid. TODO: XXX-JPS use instance in the future http://pypi.python.org/pypi/uuid/ to generate a uuid stored as private property. """ return '%s:%s' % (aq_base(self).getUid(), self.getRevision()) security.declareProtected(Permissions.View, 'hasConversion') 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: self.getConversion(**kw) return True except KeyError: return False security.declareProtected(Permissions.ModifyPortalContent, 'setConversion') def setConversion(self, data, mime=None, calculation_time=None, **kw): """ """ cache_id = '%s%s' % (self._getCacheKey(), self.generateCacheId(**kw)) if self.isTempObject(): if getattr(aq_base(self), 'temp_conversion_data', None) is None: self.temp_conversion_data = {} self.temp_conversion_data[cache_id] = (mime, aq_base(data)) return cache_factory = self._getCacheFactory() cache_duration = cache_factory.cache_duration if data is not None: for cache_plugin in cache_factory.getCachePluginList(): cache_plugin.set(cache_id, DEFAULT_CACHE_SCOPE, (self.getContentMd5(), mime, aq_base(data)), calculation_time=calculation_time, cache_duration=cache_duration) security.declareProtected(Permissions.View, 'getConversion') def getConversion(self, **kw): """ """ cache_id = '%s%s' % (self._getCacheKey(), self.generateCacheId(**kw)) if self.isTempObject(): return getattr(aq_base(self), 'temp_conversion_data', {})[cache_id] for cache_plugin in self._getCacheFactory().getCachePluginList(): cache_entry = cache_plugin.get(cache_id, DEFAULT_CACHE_SCOPE) if cache_entry is not None: data_list = cache_entry.getValue() if data_list: md5sum, mime, data = data_list if md5sum != self.getContentMd5(): raise KeyError, 'Conversion cache key is compromised for %r' % cache_id return mime, data raise KeyError, 'Conversion cache key does not exists for %r' % cache_id security.declareProtected(Permissions.View, 'getConversionSize') def getConversionSize(self, **kw): """ """ try: mime, data = self.getConversion(**kw) return len(data) except KeyError: return 0 def generateCacheId(self, **kw): """Generate proper cache id based on **kw. Function inspired from ERP5Type.Cache """ return str(makeSortedTuple(kw)).translate(string.maketrans('', ''), '[]()<>\'", ') security.declareProtected(Permissions.ModifyPortalContent, 'updateContentMd5') def updateContentMd5(self): """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() if data is not None: data = str(data) # Usefull for Pdata self._setContentMd5(md5.new(data).hexdigest()) # Reindex is useless