[Zope3-checkins] CVS: Zope3/src/zope/app/file - __init__.py:1.1.2.1 configure.zcml:1.1.2.1 file.py:1.1.2.1 fssync.py:1.1.2.1 image.py:1.1.2.1 interfaces.py:1.1.2.1

Philipp von Weitershausen philikon at philikon.de
Fri Feb 20 14:39:48 EST 2004


Update of /cvs-repository/Zope3/src/zope/app/file
In directory cvs.zope.org:/tmp/cvs-serv21299

Added Files:
      Tag: philikon-movecontent-branch
	__init__.py configure.zcml file.py fssync.py image.py 
	interfaces.py 
Log Message:
File and Image content types live in zope.app.file now.


=== Added File Zope3/src/zope/app/file/__init__.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id: __init__.py,v 1.1.2.1 2004/02/20 19:39:47 philikon Exp $
"""

from file import File
from image import Image


=== Added File Zope3/src/zope/app/file/configure.zcml ===
<configure
    xmlns='http://namespaces.zope.org/zope'
    xmlns:fssync='http://namespaces.zope.org/fssync'
    i18n_domain='zope'
    >

  <!-- Module alias for backward compat -->

  <modulealias
      module=".file"
      alias="zope.app.content.file"
      />

  <modulealias
      module=".interfaces"
      alias="zope.app.interfaces.content.file"
      />

  <modulealias
      module=".image"
      alias="zope.app.content.image"
      />

  <modulealias
      module=".interfaces"
      alias="zope.app.interfaces.content.image"
      />


  <!-- setting up content types -->

  <interface 
      interface=".interfaces.IFile" 
      type="zope.app.interfaces.content.IContentType"
      /> 

  <interface 
      interface=".interfaces.IImage" 
      type="zope.app.interfaces.content.IContentType"
      /> 

  <permission
      id="zope.AddImages"
      title="[add-images-permission] Add Images"
      />


  <!-- content classes -->

  <content class=".file.File">
    <factory
        id="File"
        permission="zope.ManageContent"
        title="File"
        description="A File"
        />

    <require
        permission="zope.View"
        interface=".interfaces.IReadFile"
        />

    <require
        permission="zope.ManageContent"
        interface=".interfaces.IWriteFile"
        set_schema=".interfaces.IReadFile"
        />

    <implements
       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
       />
  </content>

  <content class=".image.Image">
    <factory
        id="Image"
        permission="zope.ManageContent"
        title="Image"
        description="An Image"
        />

    <require
        permission="zope.View"
        interface="zope.app.file.interfaces.IReadFile"
        attributes="getImageSize"
        />

    <require
        permission="zope.ManageContent"
        interface="zope.app.file.interfaces.IWriteFile"
        set_schema="zope.app.file.interfaces.IReadFile"
        />

    <implements
        interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
        />
  </content>

  <adapter
      factory=".image.ImageSized"
      provides="zope.app.interfaces.size.ISized"
      for=".interfaces.IImage"
      />


  <!-- fssync adapters -->

  <adapter 
      for=".interfaces.IFile"
      provides="zope.app.interfaces.file.IReadFile"
      factory=".file.FileReadFile"
      permission="zope.View"
      />

  <adapter 
      for=".interfaces.IFile"
      provides="zope.app.interfaces.file.IWriteFile"
      factory=".file.FileWriteFile"
      permission="zope.ManageContent"
      />

  <adapter
      for=".interfaces.IReadFile"
      provides="zope.app.interfaces.index.text.ISearchableText"
      factory=".file.SearchableText"
      />

  <fssync:adapter
      class=".file.File"
      factory=".fssync.FileAdapter"
      />

  <adapter
      for="zope.app.folder.interfaces.IFolder"
      provides="zope.app.interfaces.file.IFileFactory"
      factory=".image.FileFactory"
      permission="zope.ManageContent"
      />

  <fssync:adapter
      class=".image.Image"
      factory=".fssync.FileAdapter"
      />


  <!-- include browser package -->

  <include package=".browser" />

</configure>


=== Added File Zope3/src/zope/app/file/file.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""File content component

$Id: file.py,v 1.1.2.1 2004/02/20 19:39:47 philikon Exp $
"""
from persistence import Persistent
from transaction import get_transaction
from zope.interface import implements

from zope.publisher.browser import FileUpload
from interfaces import IFile, IReadFile, IFileContent

__metaclass__ = type

# set the size of the chunks
MAXCHUNKSIZE = 1 << 16

class File(Persistent):
    """A persistent content component storing binary file data

    Let's test the constructor:

    >>> file = File()
    >>> file.getContentType()
    ''
    >>> file.getData()
    ''

    >>> file = File('Foobar')
    >>> file.getContentType()
    ''
    >>> file.getData()
    'Foobar'

    >>> file = File('Foobar', 'text/plain')
    >>> file.getContentType()
    'text/plain'
    >>> file.getData()
    'Foobar'

    >>> file = File(data='Foobar', contentType='text/plain')
    >>> file.getContentType()
    'text/plain'
    >>> file.getData()
    'Foobar'


    Let's test the mutators:

    >>> file = File()
    >>> file.setContentType('text/plain')
    >>> file.getContentType()
    'text/plain'

    >>> file.setData('Foobar')
    >>> file.getData()
    'Foobar'

    >>> file.edit('Blah', 'text/html')
    >>> file.getContentType()
    'text/html'
    >>> file.getData()
    'Blah'

    >>> file.setData(None)
    Traceback (most recent call last):
    ...
    TypeError: Cannot set None data on a file.


    Let's test large data input:

    >>> file = File()

    Insert as string:

    >>> file.setData('Foobar'*60000)
    >>> file.getSize()
    360000
    >>> file.getData() == 'Foobar'*60000
    True

    Insert data as FileChunk:

    >>> fc = FileChunk('Foobar'*4000)
    >>> file.setData(fc)
    >>> file.getSize()
    24000
    >>> file.getData() == 'Foobar'*4000
    True

    Insert data from file object:

    >>> import cStringIO
    >>> sio = cStringIO.StringIO()
    >>> sio.write('Foobar'*100000)
    >>> sio.seek(0)
    >>> file.setData(sio)
    >>> file.getSize()
    600000
    >>> file.getData() == 'Foobar'*100000
    True


    Last, but not least, verify the interface:

    >>> from zope.interface.verify import verifyClass
    >>> IFile.isImplementedByInstancesOf(File)
    True
    >>> verifyClass(IFile, File)
    True
    """
    
    implements(IFileContent, IFile)

    def __init__(self, data='', contentType=''):
        self.data = data
        self.contentType = contentType

    def __len__(self):
        return self.size

    def setContentType(self, contentType):
        '''See interface IFile'''
        self._contentType = contentType

    def getContentType(self):
        '''See interface IFile'''
        return self._contentType

    def edit(self, data, contentType=None):
        '''See interface IFile'''
        # XXX This seems broken to me, as setData can override the
        # content type explicitly passed in.

        if contentType is not None:
            self._contentType = contentType
        if isinstance(data, FileUpload) and not data.filename:
            data = None          # Ignore empty files
        if data is not None:
            self.data = data

    def getData(self):
        '''See interface IFile'''
        if isinstance(self._data, FileChunk):
            return str(self._data)
        else:
            return self._data

    def setData(self, data):
        '''See interface IFile'''
        # Handle case when data is a string
        if isinstance(data, unicode):
            data = data.encode('UTF-8')

        if isinstance(data, str):
            self._data, self._size = FileChunk(data), len(data)
            return

        # Handle case when data is None
        if data is None:
            raise TypeError('Cannot set None data on a file.')

        # Handle case when data is already a FileChunk
        if isinstance(data, FileChunk):
            size = len(data)
            self._data, self._size = data, size
            return

        # Handle case when data is a file object
        seek = data.seek
        read = data.read

        seek(0, 2)
        size = end = data.tell()

        if size <= 2*MAXCHUNKSIZE:
            seek(0)
            if size < MAXCHUNKSIZE:
                self._data, self._size = read(size), size
                return
            self._data, self._size = FileChunk(read(size)), size
            return

        # Make sure we have an _p_jar, even if we are a new object, by
        # doing a sub-transaction commit.
        get_transaction().savepoint()

        jar = self._p_jar

        if jar is None:
            # Ugh
            seek(0)
            self._data, self._size = FileChunk(read(size)), size
            return

        # Now we're going to build a linked list from back
        # to front to minimize the number of database updates
        # and to allow us to get things out of memory as soon as
        # possible.
        next = None
        while end > 0:
            pos = end - MAXCHUNKSIZE
            if pos < MAXCHUNKSIZE:
                pos = 0 # we always want at least MAXCHUNKSIZE bytes
            seek(pos)
            data = FileChunk(read(end - pos))

            # Woooop Woooop Woooop! This is a trick.
            # We stuff the data directly into our jar to reduce the
            # number of updates necessary.
            data._p_jar = jar

            # This is needed and has side benefit of getting
            # the thing registered:
            data.next = next

            # Now make it get saved in a sub-transaction!
            get_transaction().savepoint()

            # Now make it a ghost to free the memory.  We
            # don't need it anymore!
            data._p_changed = None

            next = data
            end = pos

        self._data, self._size = next, size
        return

    def getSize(self):
        '''See interface IFile'''
        return self._size

    data = property(getData, setData, None,
                    """Contains the data of the file.""")

    contentType = property(getContentType, setContentType, None,
                           """Specifies the content type of the data.""")

    size = property(getSize, None, None,
                    """Specifies the size of the file in bytes. Read only.""")


# Adapter for ISearchableText

from zope.app.interfaces.index.text import ISearchableText

class SearchableText:

    implements(ISearchableText)
    __used_for__ = IReadFile

    def __init__(self, file):
        self.file = file

    def getSearchableText(self):
        if self.file.contentType == "text/plain":
            return [unicode(self.file.data)]
        else:
            return None


class FileChunk(Persistent):
    # Wrapper for possibly large data

    next = None

    def __init__(self, data):
        self._data = data

    def __getslice__(self, i, j):
        return self._data[i:j]

    def __len__(self):
        data = str(self)
        return len(data)

    def __str__(self):
        next = self.next
        if next is None:
            return self._data

        result = [self._data]
        while next is not None:
            self = next
            result.append(self._data)
            next = self.next

        return ''.join(result)

class FileReadFile:
    """Adapter for file-system style read access.

    >>> file = File()
    >>> content = "This is some file\\ncontent."
    >>> file.edit(content, 'text/plain')
    >>> FileReadFile(file).read() == content
    True
    >>> FileReadFile(file).size() == len(content)
    True
    """

    def __init__(self, context):
        self.context = context

    def read(self):
        return self.context.getData()

    def size(self):
        return len(self.context.getData())

class FileWriteFile:
    """Adapter for file-system style write access.

    >>> file = File()
    >>> content = "This is some file\\ncontent."
    >>> FileWriteFile(file).write(content)
    >>> file.getData() == content
    True
    """

    def __init__(self, context):
        self.context = context

    def write(self, data):
        self.context.setData(data)


=== Added File Zope3/src/zope/app/file/fssync.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Filesystem synchronization support.

$Id: fssync.py,v 1.1.2.1 2004/02/20 19:39:47 philikon Exp $
"""
from zope.interface import implements
from zope.fssync.server.entryadapter import ObjectEntryAdapter
from zope.fssync.server.interfaces import IObjectFile

class FileAdapter(ObjectEntryAdapter):
    """ObjectFile adapter for file objects.
    """
    implements(IObjectFile)

    def getBody(self):
        return self.context.getData()

    def setBody(self, data):
        self.context.setData(data)

    def extra(self):
        return AttrMapping(self.context, ('contentType',))


=== Added File Zope3/src/zope/app/file/image.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id: image.py,v 1.1.2.1 2004/02/20 19:39:47 philikon Exp $
"""
import struct
from cStringIO import StringIO

from zope.interface import implements

from zope.app.interfaces.size import ISized
from zope.app.size import byteDisplay
from zope.app.content_types import guess_content_type
from zope.app.i18n import ZopeMessageIDFactory as _

from file import File
from interfaces import IImage

__metaclass__ = type

class Image(File):
    implements(IImage)

    def __init__(self, data=''):
        '''See interface IFile'''
        self.contentType, self._width, self._height = getImageInfo(data)
        self.data = data

    def setData(self, data):
        super(Image, self).setData(data)

        contentType, self._width, self._height = getImageInfo(self.data)
        if contentType:
            self.contentType = contentType

    def getImageSize(self):
        '''See interface IImage'''
        return (self._width, self._height)

    data = property(File.getData, setData, None,
                    """Contains the data of the file.""")


class ImageSized:

    implements(ISized)

    def __init__(self, image):
        self._image = image

    def sizeForSorting(self):
        'See ISized'
        return ('byte', self._image.getSize())

    def sizeForDisplay(self):
        'See ISized'
        w, h = self._image.getImageSize()
        if w < 0:
            w = '?'
        if h < 0:
            h = '?'
        bytes = self._image.getSize()
        byte_size = byteDisplay(bytes)
        mapping = byte_size.mapping
        size = _(byte_size + ' ${width}x${height}')
        mapping.update({'width': str(w), 'height': str(h)})
        size.mapping = mapping 
        return size


def getImageInfo(data):
    data = str(data)
    size = len(data)
    height = -1
    width = -1
    content_type = ''

    # handle GIFs
    if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
        # Check to see if content_type is correct
        content_type = 'image/gif'
        w, h = struct.unpack("<HH", data[6:10])
        width = int(w)
        height = int(h)

    # See PNG v1.2 spec (http://www.cdrom.com/pub/png/spec/)
    # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
    # and finally the 4-byte width, height
    elif ((size >= 24) and data.startswith('\211PNG\r\n\032\n')
          and (data[12:16] == 'IHDR')):
        content_type = 'image/png'
        w, h = struct.unpack(">LL", data[16:24])
        width = int(w)
        height = int(h)

    # Maybe this is for an older PNG version.
    elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'):
        # Check to see if we have the right content type
        content_type = 'image/png'
        w, h = struct.unpack(">LL", data[8:16])
        width = int(w)
        height = int(h)

    # handle JPEGs
    elif (size >= 2) and data.startswith('\377\330'):
        content_type = 'image/jpeg'
        jpeg = StringIO(data)
        jpeg.read(2)
        b = jpeg.read(1)
        try:
            while (b and ord(b) != 0xDA):
                while (ord(b) != 0xFF): b = jpeg.read(1)
                while (ord(b) == 0xFF): b = jpeg.read(1)
                if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
                    jpeg.read(3)
                    h, w = struct.unpack(">HH", jpeg.read(4))
                    break
                else:
                    jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
                b = jpeg.read(1)
            width = int(w)
            height = int(h)
        except struct.error:
            pass
        except ValueError:
            pass

    return content_type, width, height


class FileFactory:

    def __init__(self, context):
        self.context = context

    def __call__(self, name, content_type, data):
        if not content_type and data:
            content_type, width, height = getImageInfo(data)
        if not content_type:
            content_type, encoding = guess_content_type(name, data, '')

        if content_type.startswith('image/'):
            return Image(data)

        return File(data, content_type)


=== Added File Zope3/src/zope/app/file/interfaces.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Basic File interfaces.

$Id: interfaces.py,v 1.1.2.1 2004/02/20 19:39:47 philikon Exp $
"""
import zope.schema
from zope.interface import Interface
from zope.app.i18n import ZopeMessageIDFactory as _

class IReadFile(Interface):

    contentType = zope.schema.BytesLine(
        title = _(u'Content Type'),
        description=_(u'The content type identifies the type of data.'),
        default = 'text/plain',
        required=False
        )

    data = zope.schema.Bytes(
        title = _(u'Data'),
        description = _(u'The actual content of the object.'),
        default='',
        required=False,
        )

    def getData():
        """Return the contained data of the object."""

    def getContentType():
        """Returns the content type of the file using mime-types syntax."""

    def getSize():
        """Return the byte-size of the data of the object."""


class IWriteFile(Interface):

    def edit(data, contentType=None):
        """Sets the data and the content type for the object.

           Since some implementations will provide their content type
           through the data, it is good to leave the argument optional.
        """

    def setData(data):
        """Rewrite the 'file'."""

    def setContentType(contentType):
        """Sets the content type of the file."""


class IFile(IReadFile, IWriteFile):
    """The basic methods that are required to implement
       a file as a Zope Content object.

    """

class IFileContent(Interface):
    """Marker interface for content that can be managed as files.

    The default view for file content has effective URLs that don't end in
    /.  In particular, if the content included HTML, relative links in
    the HTML are relative to the container the content is in.
    """


class IImage(IFile):
    """This interface defines an Image that can be displayed.
    """

    def getImageSize():
        """Return a tuple (x, y) that describes the dimensions of
        the object.
        """




More information about the Zope3-Checkins mailing list