[Zodb-checkins] SVN: ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/ Refactor some behavior of blob and blobfile (transparent), account for append mode not failing if a file doesn't exist (it acts like write mode in this situation).

Chris McDonough chrism at plope.com
Thu Mar 24 17:15:54 EST 2005


Log message for revision 29673:
  Refactor some behavior of blob and blobfile (transparent), account for append mode not failing if a file doesn't exist (it acts like write mode in this situation).
  

Changed:
  U   ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/Blob.py
  U   ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/tests/connection.txt

-=-
Modified: ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/Blob.py
===================================================================
--- ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/Blob.py	2005-03-24 22:13:07 UTC (rev 29672)
+++ ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/Blob.py	2005-03-24 22:15:53 UTC (rev 29673)
@@ -12,11 +12,6 @@
 from transaction.interfaces import IDataManager
 from persistent import Persistent
 
-try:
-    from ZPublisher.Iterators import IStreamIterator
-except ImportError:
-    IStreamIterator = None
-
 class Blob(Persistent):
  
     implements(IBlob)
@@ -51,9 +46,6 @@
             result = BlobFile(self._p_blob_uncommitted, mode, self)
 
         if mode.startswith("a"):
-            if self._current_filename() is None:
-                raise BlobError, "Blob does not exist."
-
             if self._p_blob_readers != 0:
                 raise BlobError, "Already opened for reading."
 
@@ -66,28 +58,60 @@
             else:
                 # Re-use existing working copy
                 uncommitted = BlobFile(self._p_blob_uncommitted, mode, self)
-            
+
             self._p_blob_writers +=1
             result = uncommitted
 
         if result is not None:
+
+            # we register ourselves as a data manager with the
+            # transaction machinery in order to be notified of
+            # commit/vote/abort events.  We do this because at
+            # transaction boundaries, we need to fix up _p_ reference
+            # counts that keep track of open readers and writers and
+            # close any writable filehandles we've opened.
+
             dm = BlobDataManager(self, result)
             transaction.get().register(dm)
-            return result
 
+        return result
+
     # utility methods
 
     def _current_filename(self):
         return self._p_blob_uncommitted or self._p_blob_data
 
+    def _change(self):
+        self._p_changed = 1
+
+    def _rc_clear(self):
+        self._p_blob_readers = 0
+        self._p_blob_writers = 0
+
+    def _rc_decref(self, mode):
+        if mode.startswith('r') or mode == 'U':
+            self._p_blob_readers = max(0, self._p_blob_readers - 1)
+        elif mode.startswith('w') or mode.startswith('a'):
+            self._p_blob_writers = max(0, self._p_blob_writers - 1)
+        else:
+            raise AssertionError, 'Unknown mode %s' % mode
+
+    def _get_refcounts(self):
+        # used by unit tests
+        return self._p_blob_readers, self._p_blob_writers
+
 class BlobDataManager:
     """Special data manager to handle transaction boundaries for blobs.
 
-    Blobs need some special care taking on transaction boundaries. As 
-    a) the ghost objects might get reused, the _p_ attributes must be
-       set to a consistent state
+    Blobs need some special care taking on transaction boundaries. As
+
+    a) the ghost objects might get reused, the _p_ reader and writer
+       refcount attributes must be set to a consistent state
     b) the file objects might get passed out of the thread/transaction
        and must deny any relationship to the original blob.
+    c) writable blob filehandles must be closed at the end of a txn so
+       as to not allow reuse between two transactions.
+
     """
 
     implements(IDataManager)
@@ -95,14 +119,9 @@
     def __init__(self, blob, filehandle):
         self.blob = blob
         self.filehandle = filehandle
-        self.isSub = False
-        self._sortkey = time.time()
+        self.subtransaction = False
+        self.sortkey = time.time()
 
-    def _cleanUpBlob(self):
-        self.blob._p_blob_readers = 0
-        self.blob._p_blob_writers = 0
-        self.filehandle.cleanTransaction()
-
     def abort_sub(self, transaction):
         pass
 
@@ -110,26 +129,29 @@
         pass
 
     def tpc_begin(self, transaction, subtransaction=False):
-        self.isSub = subtransaction
+        self.subtransaction = subtransaction
 
     def tpc_abort(self, transaction):
-        self._cleanUpBlob()
+        pass
 
     def tpc_finish(self, transaction):
-        self.isSub = False
+        self.subtransaction = False
 
     def tpc_vote(self, transaction):
-        if not self.isSub:
-            self._cleanUpBlob()
+        pass
                 
     def commit(self, object, transaction):
-        pass
+        if not self.subtransaction:
+            self.blob._rc_clear() # clear all blob refcounts
+            self.filehandle.close()
 
     def abort(self, object, transaction):
-        self._cleanUpBlob()
+        if not self.subtransaction:
+            self.blob._rc_clear()
+            self.filehandle.close()
 
     def sortKey(self):
-        return self._sortkey
+        return self.sortkey
 
     def beforeCompletion(self, transaction):
         pass
@@ -138,59 +160,38 @@
         pass
 
 class BlobFile(file):
+    """ A BlobFile is a file that can be used within a transaction boundary """
+    
 
     # XXX those files should be created in the same partition as
     # the storage later puts them to avoid copying them ...
 
-    if IStreamIterator is not None:
-        __implements__ = (IStreamIterator,)
-
     def __init__(self, name, mode, blob):
         super(BlobFile, self).__init__(name, mode)
         self.blob = blob
         self.streamsize = 1<<16
 
-    def _p_changed(self):
-        if self.blob is not None:
-            self.blob._p_changed = 1
-
     def write(self, data):
         super(BlobFile, self).write(data)
-        self._p_changed()
+        self.blob._change()
 
     def writelines(self, lines):
         super(BlobFile, self).writelines(lines)
-        self._p_changed()
+        self.blob._change()
 
-    def truncate(self, size):
+    def truncate(self, size=0):
         super(BlobFile, self).truncate(size)
-        self._p_changed()
+        self.blob._change()
         
     def close(self):
-        if self.blob is not None:
-            if (self.mode.startswith("w") or
-                self.mode.startswith("a")):
-                self.blob._p_blob_writers -= 1
-            else:
-                self.blob._p_blob_readers -= 1
+        self.blob._rc_decref(self.mode)
         super(BlobFile, self).close()
 
-    def cleanTransaction(self):
-        self.blob = None
-
     def next(self):
         data = self.read(self.streamsize)
         if not data:
             if self.blob is not None:
-                self.blob._p_blob_readers -= 1
+                self.blob._rc_decref(self.mode)
             raise StopIteration
         return data
 
-    def __len__(self):
-        cur_pos = self.tell()
-        self.seek(0, 2)
-        size = self.tell()
-        self.seek(cur_pos, 0)
-        return size
-
-

Modified: ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/tests/connection.txt
===================================================================
--- ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/tests/connection.txt	2005-03-24 22:13:07 UTC (rev 29672)
+++ ZODB/branches/ctheune-blobsupport/src/ZODB/Blobs/tests/connection.txt	2005-03-24 22:15:53 UTC (rev 29673)
@@ -15,8 +15,7 @@
 Connection support for Blobs tests
 ==================================
 
-Connections handle Blobs specially. To demonstrate that, we first
-need a Blob with some data:
+Connections handle Blobs specially. To demonstrate that, we first need a Blob with some data:
 
     >>> from ZODB.Blobs.interfaces import IBlob
     >>> from ZODB.Blobs.Blob import Blob



More information about the Zodb-checkins mailing list