import gzip, hashlib from roundup import hyperdb from StringIO import StringIO def interceptor_factory(classname, parent, baseclass) : ''' Creates a subclass of parent which is also a subclass of the specified base class. The base class is saved as class variable on the derived subclass. ''' attrs = {'super' : baseclass } return type(classname, (parent, baseclass, object), attrs) class ClassInterceptor : ''' This class provides hooks to the create, get and get methods of a superclass which is defined at runtime. This allows children of this class to override the set and get methods with their own functionality without re-implementing what has already been written. It is designed to be used with the interceptor_factory() method in this class. ''' def super_get(self, *p, **kw): ''' Convenience method to call the get() method of the superclass. ''' return self.super.get(self, *p, **kw) def super_set(self, *p, **kw): ''' Convenience method to call the set() method of the superclass. ''' return self.super.set(self, *p, **kw) def super_create(self, *p, **kw): ''' Convenience method to call the create() method of the superclass. ''' return self.super.create(self, *p, **kw) class GzipFileClass (ClassInterceptor) : ''' This is designed to be a mixin with the FileClass implementation of the backend. It provides transparent compression and decompression of file objects. Note that only the contents of the file are affected (and not the file name on disk, as FileClass does not have control over file naming. ''' def _compress_content(self, propvalues): ''' The compression code is factored out into its own method becuase it is needed for create() and set() ''' if 'content' in propvalues : content = propvalues['content'] compressed_fobj = StringIO() compressor = gzip.GzipFile(fileobj=compressed_fobj,mode="wb") compressor.write(content) compressor.close() propvalues['content'] = compressed_fobj.getvalue() def create(self, **propvalues): ''' This method intercepts the "content" property if present. All other properties remain unaffected. The content property is compressed with gzip compression, then handed off to the superclass. ''' self._compress_content(propvalues) return self.super_create(**propvalues) def set(self, itemid, **propvalues): ''' This method intercepts the "content" property if present. All other properties remain unaffected. The content property is compressed with gzip compression, then handed off to the superclass for storage. ''' self._compress_content(propvalues) return self.super_set(itemid, **propvalues) def get(self, nodeid, propname, *p, **kw): ''' This method intercepts the content property if present. No other properties are affected. The content property is uncompressed before it is returned. If the file is not a gzipped file, the content is just directly returned. ''' data = self.super_get(nodeid, propname, *p, **kw) if propname == 'content' : compressed_content = data compressed_fobj = StringIO(compressed_content) decompressor = gzip.GzipFile(fileobj=compressed_fobj,mode='rb') try : data = decompressor.read() except IOError : pass decompressor.close() return data class Md5FileClass (ClassInterceptor): ''' This class creates another property of FileClass (md5hash) which is automatically maintained. This new property is the hash of the content property, and is updated whenever the content property is set. The user is never allowed to set the md5hash property directly. The md5sum property is visible to the user, so there is no need to override the get() method. ''' def __init__(self, db, classname, **properties): ''' Add (or overwrite) the md5sum property to ensure that it exists. ''' properties['md5hash'] = hyperdb.String(indexme='yes') self.super.__init__(self,db,classname,**properties) def _hash_content(self, propvalues): # do not let user set md5hash if 'md5hash' in propvalues : del propvalues['md5hash'] if 'content' in propvalues : sh = hashlib.md5() sh.update(propvalues['content']) propvalues['md5hash'] = sh.hexdigest() def create(self, **propvalues): self._hash_content(propvalues) return self.super_create(**propvalues) def set(self, itemid, **propvalues): self._hash_content(propvalues) return self.super_set(itemid, **propvalues) class UniqueFileClass (Md5FileClass): ''' This class intercepts requests to create new objects and refuses to create duplicates. If the "new" file has the same content as an existing file, the existing ID is returned. Otherwise, the file is created as specified. Note that the only property used in the determination of "same or different" is the content property. If the content is the same, but something else is different, the new values in the other properties are ignored when the existing ID is returned. Note that this only affects the creation of new files. After a file is created, you can set it's content property to the same value as some other file. ''' def create(self, **propvalues): '''Intercepts file creation requests and creates only those files which do not already exist in the database. If the file already exists, the existing ID is returned. Note that due to the use of old-style classes in Roundup, I had to hard-code the deferred create() method to the Md5FileClass class. ''' if 'content' in propvalues : sh = hashlib.md5() sh.update(propvalues['content']) hash = sh.hexdigest() myclass = self.db.getclass(self.classname) existing_id = myclass.filter(None, {'md5hash':hash}) if existing_id and len(existing_id) > 0 : return existing_id[0] return Md5FileClass.create(self,**propvalues) #SHA: 615b532ce82cb1a3f8893a1153cbda3aeb82f68d