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
