[Zope3-checkins] CVS: Zope3/src/zope/app/file - __init__.py:1.2
configure.zcml:1.2 file.py:1.2 fssync.py:1.2 image.py:1.2
interfaces.py:1.2
Philipp von Weitershausen
philikon at philikon.de
Tue Feb 24 11:50:19 EST 2004
Update of /cvs-repository/Zope3/src/zope/app/file
In directory cvs.zope.org:/tmp/cvs-serv26210/src/zope/app/file
Added Files:
__init__.py configure.zcml file.py fssync.py image.py
interfaces.py
Log Message:
Combined the File and Image content types in their own package below
zope.app, including their interfaces and browser views.
=== Zope3/src/zope/app/file/__init__.py 1.1 => 1.2 ===
--- /dev/null Tue Feb 24 11:50:18 2004
+++ Zope3/src/zope/app/file/__init__.py Tue Feb 24 11:49:48 2004
@@ -0,0 +1,19 @@
+##############################################################################
+#
+# 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$
+"""
+
+from file import File
+from image import Image
=== Zope3/src/zope/app/file/configure.zcml 1.1 => 1.2 ===
--- /dev/null Tue Feb 24 11:50:18 2004
+++ Zope3/src/zope/app/file/configure.zcml Tue Feb 24 11:49:48 2004
@@ -0,0 +1,151 @@
+<configure
+ xmlns='http://namespaces.zope.org/zope'
+ xmlns:fssync='http://namespaces.zope.org/fssync'
+ i18n_domain='zope'
+ >
+
+ <!-- Module alias for backward compat
+ Will go away once we have a conversion script -->
+
+ <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>
=== Zope3/src/zope/app/file/file.py 1.1 => 1.2 ===
--- /dev/null Tue Feb 24 11:50:18 2004
+++ Zope3/src/zope/app/file/file.py Tue Feb 24 11:49:48 2004
@@ -0,0 +1,339 @@
+##############################################################################
+#
+# 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$
+"""
+from persistent 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().commit(1)
+
+ 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)
=== Zope3/src/zope/app/file/fssync.py 1.1 => 1.2 ===
--- /dev/null Tue Feb 24 11:50:18 2004
+++ Zope3/src/zope/app/file/fssync.py Tue Feb 24 11:49:48 2004
@@ -0,0 +1,34 @@
+##############################################################################
+#
+# 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$
+"""
+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',))
=== Zope3/src/zope/app/file/image.py 1.1 => 1.2 ===
--- /dev/null Tue Feb 24 11:50:18 2004
+++ Zope3/src/zope/app/file/image.py Tue Feb 24 11:49:48 2004
@@ -0,0 +1,152 @@
+##############################################################################
+#
+# 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$
+"""
+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
+
+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)
+
+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
=== Zope3/src/zope/app/file/interfaces.py 1.1 => 1.2 ===
--- /dev/null Tue Feb 24 11:50:19 2004
+++ Zope3/src/zope/app/file/interfaces.py Tue Feb 24 11:49:48 2004
@@ -0,0 +1,86 @@
+##############################################################################
+#
+# 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$
+"""
+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