[Zope3-checkins] SVN: Zope3/branches/jhauser-filefieldwidget/src/zope/app/f Change File implementation for directly use the contents attribute in widgets

Roger Ineichen roger at projekt01.ch
Sat Jan 22 06:16:29 EST 2005


Log message for revision 28914:
  Change File implementation for directly use the contents attribute in widgets
  Added some BBB warnings
  Added tests

Changed:
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py
  U   Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/schemawidgets.py

-=-
Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py	2005-01-22 02:40:04 UTC (rev 28913)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/file/file.py	2005-01-22 11:16:29 UTC (rev 28914)
@@ -17,6 +17,7 @@
 """
 __docformat__ = 'restructuredtext'
 
+from warnings import warn
 from persistent import Persistent
 from transaction import get_transaction
 
@@ -428,6 +429,92 @@
     Let's test the constructor:
 
     >>> file = File()
+    >>> file.open('r').read()
+    ''
+
+    >>> file = File('Foobar')
+    >>> file.contentType
+    ''
+    >>> file.open('r').read()
+    'Foobar'
+
+    >>> file = File('Foobar', 'text/plain')
+    >>> file.contentType
+    'text/plain'
+    >>> file.open('r').read()
+    'Foobar'
+
+    >>> file = File(data='Foobar', contentType='text/plain')
+    >>> file.contentType
+    'text/plain'
+    >>> file.open('r').read()
+    'Foobar'
+
+
+    Let's test the mutators:
+
+    >>> file = File()
+    >>> file.contentType = 'text/plain'
+    >>> file.contentType
+    'text/plain'
+
+    >>> file.open('w').write('Foobar')
+    >>> file.open('r').read()
+    'Foobar'
+
+    >>> file.open('w').write(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.open('w').write('Foobar'*60000)
+    >>> file.getSize()
+    360000
+    >>> file.open('r').read() == 'Foobar'*60000
+    True
+
+    Insert data as FileChunk:
+
+    >>> fc = FileChunk('Foobar'*4000)
+    >>> file.open('w').write(fc)
+    >>> file.getSize()
+    24000
+    >>> file.open('r').read() == 'Foobar'*4000
+    True
+
+    Insert data from file object:
+
+    >>> import cStringIO
+    >>> sio = cStringIO.StringIO()
+    >>> sio.write('Foobar'*100000)
+    >>> sio.seek(0)
+    >>> file.open('w').write(sio)
+    >>> file.getSize()
+    600000
+    >>> file.open('r').read() == 'Foobar'*100000
+    True
+
+
+    Last, but not least, verify the interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> IFile.implementedBy(File)
+    True
+    >>> verifyClass(IFile, File)
+    True
+
+    BBB: test backward compatibility:
+
+    >>> file = File()
+    >>> file.contentType
+    ''
     >>> file.data
     ''
 
@@ -467,9 +554,10 @@
     TypeError: Cannot set None data on a file.
 
 
-    Let's test large data input:
+    Let's test large data input for BBB files:
 
     >>> file = File()
+    >>> file._contents = None
 
     Insert as string:
 
@@ -500,23 +588,31 @@
     >>> 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(IFile, IMime, IFileContent)
     
+    # BBB: set the _contents = None, if we have a Mime object stored
+    # under the _contents attr, we have a new style file.
+    # I f we test old style files we have to set the _contents to None 
+    # after initializing
+    _contents = None
+    
     def __init__(self, data='', contentType=''):
         self._contents = Mime()
         self.open(mode='w').write(data)
-        # BBB: map it to the right contentType
+        
+        # BBB: map contentType to the right value for new style file
         self.contentType = contentType
+        self.data = data
+        
+    def isNewStyle(self):
+        if self._contents is None:
+            warn("The File implementation has ben changes, migrate your class",
+                DeprecationWarning, 2)
+            return False
+        else:
+            return True
 
     # TODO: Fix the widgets for to store the data
     # now we get a Mime instance form the widget, but _get/_setContents 
@@ -527,10 +623,10 @@
     # This whould break everything... hm, perhaps we can use the data property 
     # for BBB and a access directly to the file data.
     def _getContents(self):
-        return removeSecurityProxy(self._contents.data)
+        return self._contents
 
-    def _setContents(self, data):
-        self._contents.data = removeSecurityProxy(data)
+    def _setContents(self, contents):
+        self._contents = contents
 
     def open(self, mode='r'):
         """return a file-like object for reading or updating the file value.
@@ -543,25 +639,116 @@
             pass
             # TODO: raise wrong file open attribute error
 
+    # BBB: supports BBB
     def getSize(self):
-        return self._contents.getSize()
+        if self.isNewStyle():
+            return self._contents.getSize()
+        else:
+            warn("The File implementation has ben changes, migrate your class",
+                DeprecationWarning, 2)
+            return self._size
     
     # See IFile.
     contents = property(_getContents, _setContents)
     #contents = FieldProperty(IFile['contents'])
     
     # BBB: remove it after removing BBB
-    # old compatibility methods
+    # TODO: add deprication warning
     def _getData(self):
-        return self._contents
+        warn("The data attribute is deprecated, migrate your File class",
+            DeprecationWarning, 2)
+        if isinstance(self._data, FileChunk):
+            return str(self._data)
+        else:
+            return self._data
 
+    # TODO: add deprication warning
     def _setData(self, data):
-        self._contents = data
+        # Handle case when data is a string
+        warn("The data attribute is deprecated, migrate your File class",
+            DeprecationWarning, 2)
+        if isinstance(data, unicode):
+            data = data.encode('UTF-8')
 
-    data = property(_getContents, _setContents)
+        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.
+            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
+
+    data = property(_getData, _setData)
+
+
+
 class ReadFileStorage(object):
     """Adapter for file-system style read access.
 

Modified: Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/schemawidgets.py
===================================================================
--- Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/schemawidgets.py	2005-01-22 02:40:04 UTC (rev 28913)
+++ Zope3/branches/jhauser-filefieldwidget/src/zope/app/form/browser/schemawidgets.py	2005-01-22 11:16:29 UTC (rev 28914)
@@ -114,16 +114,24 @@
         return content
 
     def applyChanges(self, content):
+        print ""
+        print "applyChanges"
         field = self.context
+        print "field = self.context ", self.context
 
         # create our new object value
         value = field.query(content, None)
+        print "value ", value
         if value is None:
             # TODO: ObjectCreatedEvent here would be nice
+            print "if value is None:"
             value = self.factory()
+            print "value ", value
 
         # apply sub changes, see if there *are* any changes
         # TODO: ObjectModifiedEvent here would be nice
+        print "self ", self
+        print "self.names ", self.names
         changes = applyWidgetsChanges(self, field.schema, target=value,
                                       names=self.names)
 



More information about the Zope3-Checkins mailing list