from os import fstat
from zope.interface import implements
from StringIO import StringIO
from Acquisition import Implicit, aq_base
from AccessControl import ClassSecurityInfo
from ComputedAttribute import ComputedAttribute
    from App.class_init import InitializeClass
    InitializeClass     # keep pyflakes happy...
except ImportError:
    from Globals import InitializeClass
from ZODB.blob import Blob
from persistent import Persistent
from transaction import savepoint
from webdav.common import rfc1123_date
from Products.CMFCore.permissions import View
from Products.Archetypes.atapi import ObjectField, FileWidget, ImageWidget
from Products.Archetypes.atapi import PrimaryFieldMarshaller
from Products.Archetypes.Registry import registerField
from Products.Archetypes.utils import contentDispositionHeader
from plone.i18n.normalizer.interfaces import IUserPreferredFileNameNormalizer
from import IBlobbable, IWebDavUpload, IBlobField
from import IBlobImageField
from import IBlobWrapper
from import BlobStreamIterator
from import handleIfModifiedSince, handleRequestRange
from import ImageFieldMixin
from import getImageSize, getPILResizeAlgo, openBlob
from import blobScalesAttr
class WebDavUpload(object):
    """ helper class when handling webdav uploads;  the class is needed
        to be able to provide an adapter for this way of creating a blob """
    def __init__(self, file, filename=None, mimetype=None, context=None, **kwargs):
        self.file = file
        if hasattr(aq_base(context), 'getFilename'):
            filename = context.getFilename() or filename
        self.filename = filename
        self.mimetype = mimetype
        self.kwargs = kwargs
class BlobMarshaller(PrimaryFieldMarshaller):
    def demarshall(self, instance, data, **kwargs):
        p = instance.getPrimaryField()
        mutator = p.getMutator(instance)
class BlobWrapper(Implicit, Persistent):
    """ persistent wrapper for a zodb blob, also holding some metadata """
    security = ClassSecurityInfo()
    def __init__(self, content_type):
        self.blob = Blob()
        self.content_type = content_type
        self.filename = None
    security.declareProtected(View, 'index_html')
    def index_html(self, REQUEST=None, RESPONSE=None, charset='utf-8', disposition='inline'):
        """ make it directly viewable when entering the objects URL """
        if REQUEST is None:
            REQUEST = self.REQUEST
        if RESPONSE is None:
        RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
        RESPONSE.setHeader('Content-Type', self.getContentType())
        RESPONSE.setHeader('Accept-Ranges', 'bytes')
        if handleIfModifiedSince(self, REQUEST, RESPONSE):
            return ''
        length = self.get_size()
        RESPONSE.setHeader('Content-Length', length)
        filename = self.getFilename()
        if filename is not None:
            if not isinstance(filename, unicode):
                filename = unicode(filename, charset, errors="ignore")
            filename = IUserPreferredFileNameNormalizer(REQUEST).normalize(
            header_value = contentDispositionHeader(
            RESPONSE.setHeader("Content-disposition", header_value)
        request_range = handleRequestRange(self, length, REQUEST, RESPONSE)
        return self.getIterator(**request_range)
    def setBlob(self, blob):
        """ set the contained blob object """
        self.blob = blob
    def getBlob(self):
        """ return the contained blob object """
        return self.blob
    def getIterator(self, **kw):
        """ return a filestream iterator object from the blob """
        return BlobStreamIterator(self.blob, **kw)
    security.declareProtected(View, 'get_size')
    def get_size(self):
        """ return the size of the blob """
        blob = openBlob(self.blob)
        size = fstat(blob.fileno()).st_size
        return size
    __len__ = get_size
    def __nonzero__(self):
        # count as having a value unless we lack both data and a filename
        return bool(self.filename or len(self))
    security.declareProtected(View, 'getSize')
    def getSize(self):
        """ return image dimensions of the blob """
        # TODO: this should probably be cached...
        blob = openBlob(self.blob)
        size = getImageSize(blob)
        return size
    security.declareProtected(View, 'width')
    def width(self):
        """ provide the image width as an attribute """
        width, height = self.getSize()
        return width
    security.declareProtected(View, 'height')
    def height(self):
        """ provide the image height as an attribute """
        width, height = self.getSize()
        return height
    def setContentType(self, value):
        """ set mimetype for this blob """
        value = str(value).split(';')[0].strip()    # might be like: text/plain; charset='utf-8'
        self.content_type = value
    def getContentType(self):
        """ return mimetype for this blob """
        return self.content_type
    def setFilename(self, value):
        """ set filename for this blob """
        if isinstance(value, basestring):
            value = value[max(value.rfind('/'),
                              value.rfind(':')) + 1:]
        self.filename = value
    def getFilename(self):
        """ return filename for this blob """
        return self.filename
    # compatibility methods
    def __str__(self):
        """ return data as a string;  this is highly inefficient as it
            loads the complete blob content into memory, but the method
            is unfortunately still used here and there... """
        return openBlob(self.blob).read()
    data = ComputedAttribute(__str__, 0)
class ReuseBlob(Exception):
    """ exception indicating that a blob should be reused """
class BlobField(ObjectField):
    """ file field implementation based on zodb blobs """
    _properties = ObjectField._properties.copy()
        'type': 'blob',
        'default': None,
        'primary': False,
        'widget': FileWidget,
        'default_content_type': 'application/octet-stream',
    security = ClassSecurityInfo()
    def getUnwrapped(self, instance, **kwargs):
        return super(BlobField, self).get(instance, **kwargs)
    def get(self, instance, **kwargs):
        value = super(BlobField, self).get(instance, **kwargs)
        if getattr(value, '__of__', None) is not None:
            return value.__of__(instance)
            return value
    def set(self, instance, value, **kwargs):
        """ use input value to populate the blob and set the associated
            file name and mimetype.  the latter can be overridden by an
            option "mimetype" keyword argument """
        if value in ('DELETE_FILE', 'DELETE_IMAGE'):
            super(BlobField, self).unset(instance, **kwargs)
        # create a new blob instead of modifying the old one to
        # achieve copy-on-write semantics
        blob = BlobWrapper(self.default_content_type)
        if isinstance(value, basestring):
            value = StringIO(value)     # simple strings cannot be adapted...
            setattr(value, 'filename', kwargs.get('filename', None))
        if value is not None:
            blobbable = IBlobbable(value)
            except ReuseBlob, exception:
                blob.setBlob(exception.args[0])     # reuse the given blob
            mimetype = kwargs.get('mimetype', None)
            if not mimetype:
                mimetype = blobbable.mimetype()
            if mimetype and mimetype != 'None':
            blob.setFilename(kwargs.get('filename', blobbable.filename()))
        super(BlobField, self).set(instance, blob, **kwargs)
        # a transaction savepoint is created after setting the blob's value
        # in order to make it available at its temporary path (e.g. to index
        # pdfs etc using solr & tika within the same transaction)
    def fixAutoId(self, instance):
        """ if an explicit id was given and the instance still has the
            auto-generated one it should be renamed;  also see
            `_setATCTFileContent` in ATCT's `ATCTFileContent` class """
        if not self.primary:
        filename = self.getFilename(instance)
        if filename is not None and instance._isIDAutoGenerated(instance.getId()):
            request = instance.REQUEST
            req_id = request.form.get('id')
            if req_id and not instance._isIDAutoGenerated(req_id):
                return      # don't rename if an explicit id was given
            if hasattr(aq_base(instance), '_should_set_id_to_filename'):
                # ^^ BBB for ATContentTypes <2.0
                if not instance._should_set_id_to_filename(filename, request.form.get('title')):
                    return      # don't rename now if AT should do it from title
            if not isinstance(filename, unicode):
                filename = unicode(filename, instance.getCharset())
            filename = IUserPreferredFileNameNormalizer(request).normalize(filename)
            if filename and not filename == instance.getId():
                # a file name was given, so the instance needs to be renamed...
    security.declareProtected(View, 'download')
    def download(self, instance, REQUEST=None, RESPONSE=None):
        """ download the file (use default index_html) """
        return self.index_html(instance, REQUEST, RESPONSE, disposition='attachment')
    security.declareProtected(View, 'index_html')
    def index_html(self, instance, REQUEST=None, RESPONSE=None, **kwargs):
        """ make it directly viewable when entering the objects URL """
        blob = self.get(instance, raw=True)    # TODO: why 'raw'?
        charset = instance.getCharset()
        return blob.index_html(
            charset=charset, **kwargs
    def get_size(self, instance):
        """ return the size of the blob used for get_size in BaseObject """
        blob = self.getUnwrapped(instance)
        if blob is not None:
            return blob.get_size()
            return 0
    def getContentType(self, instance, fromBaseUnit=True):
        """ return the mimetype associated with the blob data """
        blob = self.getUnwrapped(instance)
        if blob is not None:
            return blob.getContentType()
            return 'application/octet-stream'
    def getFilename(self, instance, fromBaseUnit=True):
        """ return the file name associated with the blob data """
        blob = self.getUnwrapped(instance)
        if blob is not None:
            return blob.getFilename()
            return None
registerField(BlobField, title='Blob',
              description='Used for storing files in blobs')
# convenience base classes for blob-aware file & image fields
class FileField(BlobField):
    """ base class for a blob-based file field """
    _properties = BlobField._properties.copy()
        'type': 'file',
registerField(FileField, title='Blob-aware FileField',
              description='Used for storing files in blobs')
class ImageField(BlobField, ImageFieldMixin):
    """ base class for a blob-based image field """
    _properties = BlobField._properties.copy()
        'type': 'image',
        'original_size': None,
        'max_size': None,
        'sizes': None,
        'swallowResizeExceptions': False,
        'pil_quality': 88,
        'pil_resize_algo': getPILResizeAlgo(),
        'default_content_type': 'image/png',
        'allowable_content_types': ('image/gif', 'image/jpeg', 'image/png'),
        'widget': ImageWidget,
    def set(self, instance, value, **kwargs):
        super(ImageField, self).set(instance, value, **kwargs)
        if hasattr(aq_base(instance), blobScalesAttr):
            delattr(aq_base(instance), blobScalesAttr)
registerField(ImageField, title='Blob-aware ImageField',
              description='Used for storing image in blobs')