[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/file/mimefield.py
Added initial file field definition.
Janko Hauser
jhauser at zscout.de
Sat Jan 15 17:59:34 EST 2005
Log message for revision 28845:
Added initial file field definition.
Changed:
A Zope3/trunk/src/zope/app/file/mimefield.py
-=-
Added: Zope3/trunk/src/zope/app/file/mimefield.py
===================================================================
--- Zope3/trunk/src/zope/app/file/mimefield.py 2005-01-15 16:23:27 UTC (rev 28844)
+++ Zope3/trunk/src/zope/app/file/mimefield.py 2005-01-15 22:59:33 UTC (rev 28845)
@@ -0,0 +1,265 @@
+##############################################################################
+#
+# 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.1 (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: $
+"""
+__docformat__ = 'restructuredtext'
+
+from persistent import Persistent
+from transaction import get_transaction
+from zope.interface import implements
+
+from zope.schema.interfaces import IField,IBytesLine
+from zope.schema._bootstrapfields import Field
+from zope.schema._bootstrapfields import TextLine, Int
+
+from zope.publisher.browser import FileUpload
+from zope.app.file.file import FileChunk
+from interfaces import IFile
+
+# set the size of the chunks
+MAXCHUNKSIZE = 1 << 16
+
+#
+# The basic schema interface
+#
+class IMime(IField, IBytesLine):
+ u"""Fields which hold data characterized by a mime type.
+
+ The data is stored memory effecient.
+ """
+
+ mimetype = TextLine(title=_(u"Mime type"),
+ description=_(u"The mime type of the stored data")
+ required=False,
+ default=u"application/octet-stream"
+ )
+
+ def getSize():
+ u"""Return the size of the stored data in bytes."""
+
+class IFileData(IMime):
+ u"""Fields which hold uploaded data, mainly file type data"""
+
+ filename = TextLine(title=_(u"Filename"),
+ description=_(u"The Filename of the uploaded file"),
+ required=False)
+
+
+ # The field implementation
+class FileData(BytesLine):
+ """A field implementation for uploaded files.
+
+ Let's test the constructor:
+
+ >>> file = FileData()
+ >>> file.contentType
+ ''
+ >>> file.data
+ ''
+
+ >>> file = FileData('Foobar')
+ >>> file.contentType
+ ''
+ >>> file.data
+ 'Foobar'
+
+ >>> file = FileData('Foobar', 'text/plain')
+ >>> file.contentType
+ 'text/plain'
+ >>> file.data
+ 'Foobar'
+
+ >>> file = FileData(data='Foobar', contentType='text/plain')
+ >>> file.contentType
+ 'text/plain'
+ >>> file.data
+ 'Foobar'
+
+
+ Let's test the mutators:
+
+ >>> file = FileData()
+ >>> file.contentType = 'text/plain'
+ >>> file.contentType
+ 'text/plain'
+
+ >>> file.data = 'Foobar'
+ >>> file.data
+ 'Foobar'
+
+ >>> file.data = None
+ Traceback (most recent call last):
+ ...
+ TypeError: Cannot set None data on a file.
+
+
+ Let's test large data input:
+
+ >>> file = FileData()
+
+ Insert as string:
+
+ >>> file.data = 'Foobar'*60000
+ >>> file.getSize()
+ 360000
+ >>> file.data == 'Foobar'*60000
+ True
+
+ Insert data as FileChunk:
+
+ >>> fc = FileChunk('Foobar'*4000)
+ >>> file.data = fc
+ >>> file.getSize()
+ 24000
+ >>> file.data == 'Foobar'*4000
+ True
+
+ Insert data from file object:
+
+ >>> import cStringIO
+ >>> sio = cStringIO.StringIO()
+ >>> sio.write('Foobar'*100000)
+ >>> sio.seek(0)
+ >>> file.data = sio
+ >>> file.getSize()
+ 600000
+ >>> file.data == 'Foobar'*100000
+ True
+
+
+ Last, but not least, verify the interface:
+
+ >>> from zope.interface.verify import verifyClass
+ >>> IFile.implementedBy(File)
+ True
+ >>> verifyClass(IFile, File)
+ True
+ """
+
+ implements(IFileData, IFile)
+
+ # reimplementing the old file class, which stores data in a chunked
+ # data structure. As we inherit from BytesLine we are an 'Attribute'.
+ def __init__(self, data='', contentType=''):
+ self.data = data
+ # do we need to support self.contentType for IFile?
+ self.mimeType = contentType
+
+ def _getData(self):
+ if isinstance(self._data, FileChunk):
+ return str(self._data)
+ else:
+ return self._data
+
+ def _setData(self, data):
+ # 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
+
+ # if it is a fileupload object
+ if hasattr(data,'filename'):
+ fid = data.filename
+ # prepare from ospath filenames from explorer.
+ fid=fid[max(fid.rfind('/'),
+ fid.rfind('\\'),
+ fid.rfind(':')
+ )+1:]
+ self.filename = fid
+ else:
+ self.filename = ''
+
+ 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.
+ jar.add(data)
+
+ # 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().commit(1)
+
+ # 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 `IFile`'''
+ return self._size
+
+ # See IFile.
+ data = property(_getData, _setData)
+
More information about the Zope3-Checkins
mailing list