Commit c6679cc2 authored by Jim Fulton's avatar Jim Fulton

Only support the documented modes, 'r', 'w', 'a', and 'r+' and open

all OS files in binary mode.  Binary mode for blobs is now implicit.
parent 4b6ab743
...@@ -31,23 +31,16 @@ import transaction ...@@ -31,23 +31,16 @@ import transaction
import transaction.interfaces import transaction.interfaces
from persistent import Persistent from persistent import Persistent
if sys.platform == 'win32':
import win32file
BLOB_SUFFIX = ".blob" BLOB_SUFFIX = ".blob"
valid_modes = 'r', 'w', 'r+', 'a'
class Blob(Persistent): class Blob(Persistent):
"""A BLOB supports efficient handling of large data within ZODB.""" """A BLOB supports efficient handling of large data within ZODB."""
zope.interface.implements(IBlob) zope.interface.implements(IBlob)
# Binding this to an attribute allows overriding it in the unit tests _os_link = os.rename
if sys.platform == 'win32':
_os_link = lambda self, src, dst: win32file.CreateHardLink(dst, src,
None)
else:
_os_link = os.link
_p_blob_readers = 0 _p_blob_readers = 0
_p_blob_writers = 0 _p_blob_writers = 0
...@@ -68,8 +61,11 @@ class Blob(Persistent): ...@@ -68,8 +61,11 @@ class Blob(Persistent):
def open(self, mode="r"): def open(self, mode="r"):
"""Returns a file(-like) object representing blob data.""" """Returns a file(-like) object representing blob data."""
result = None result = None
if mode not in valid_modes:
raise ValueError("invalid mode", mode)
if (mode.startswith("r") or mode=="U"): if mode == 'r':
if self._current_filename() is None: if self._current_filename() is None:
raise BlobError("Blob does not exist.") raise BlobError("Blob does not exist.")
...@@ -79,7 +75,7 @@ class Blob(Persistent): ...@@ -79,7 +75,7 @@ class Blob(Persistent):
self._p_blob_readers += 1 self._p_blob_readers += 1
result = BlobFile(self._current_filename(), mode, self) result = BlobFile(self._current_filename(), mode, self)
elif mode.startswith("w"): elif mode == 'w':
if self._p_blob_readers != 0: if self._p_blob_readers != 0:
raise BlobError("Already opened for reading.") raise BlobError("Already opened for reading.")
...@@ -88,7 +84,7 @@ class Blob(Persistent): ...@@ -88,7 +84,7 @@ class Blob(Persistent):
self._create_uncommitted_file() self._create_uncommitted_file()
result = BlobFile(self._p_blob_uncommitted, mode, self) result = BlobFile(self._p_blob_uncommitted, mode, self)
elif mode.startswith("a"): elif mode in ('a', 'r+'):
if self._p_blob_readers != 0: if self._p_blob_readers != 0:
raise BlobError("Already opened for reading.") raise BlobError("Already opened for reading.")
...@@ -233,12 +229,11 @@ class Blob(Persistent): ...@@ -233,12 +229,11 @@ class Blob(Persistent):
self._p_blob_writers = 0 self._p_blob_writers = 0
def _p_blob_decref(self, mode): def _p_blob_decref(self, mode):
if mode.startswith('r') or mode == 'U': if mode == 'r':
self._p_blob_readers = max(0, self._p_blob_readers - 1) 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: else:
raise AssertionError('Unknown mode %s' % mode) assert mode in valid_modes, "Invalid mode %r" % mode
self._p_blob_writers = max(0, self._p_blob_writers - 1)
def _p_blob_refcounts(self): def _p_blob_refcounts(self):
# used by unit tests # used by unit tests
...@@ -345,7 +340,7 @@ class BlobFile(file): ...@@ -345,7 +340,7 @@ class BlobFile(file):
# the storage later puts them to avoid copying them ... # the storage later puts them to avoid copying them ...
def __init__(self, name, mode, blob): def __init__(self, name, mode, blob):
super(BlobFile, self).__init__(name, mode) super(BlobFile, self).__init__(name, mode+'b')
self.blob = blob self.blob = blob
self.close_called = False self.close_called = False
...@@ -364,7 +359,7 @@ class BlobFile(file): ...@@ -364,7 +359,7 @@ class BlobFile(file):
def close(self): def close(self):
# we don't want to decref twice # we don't want to decref twice
if not self.close_called: if not self.close_called:
self.blob._p_blob_decref(self.mode) self.blob._p_blob_decref(self.mode[:-1])
self.close_called = True self.close_called = True
super(BlobFile, self).close() super(BlobFile, self).close()
......
...@@ -138,9 +138,9 @@ We can truncate a blob: ...@@ -138,9 +138,9 @@ We can truncate a blob:
'' ''
>>> f8.close() >>> f8.close()
We can explicitly open Blobs in the different modified modes: Blobs are always opened in binary mode:
>>> f9 = myblob.open("rb") >>> f9 = myblob.open("r")
>>> f9.mode >>> f9.mode
'rb' 'rb'
>>> f9.close() >>> f9.close()
......
...@@ -206,20 +206,20 @@ We do support optimistic savepoints :: ...@@ -206,20 +206,20 @@ We do support optimistic savepoints ::
>>> connection5 = database.open() >>> connection5 = database.open()
>>> root5 = connection5.root() >>> root5 = connection5.root()
>>> blob = Blob() >>> blob = Blob()
>>> blob_fh = blob.open("wb") >>> blob_fh = blob.open("w")
>>> blob_fh.write("I'm a happy blob.") >>> blob_fh.write("I'm a happy blob.")
>>> blob_fh.close() >>> blob_fh.close()
>>> root5['blob'] = blob >>> root5['blob'] = blob
>>> transaction.commit() >>> transaction.commit()
>>> root5['blob'].open("rb").read() >>> root5['blob'].open("r").read()
"I'm a happy blob." "I'm a happy blob."
>>> blob_fh = root5['blob'].open("a") >>> blob_fh = root5['blob'].open("a")
>>> blob_fh.write(" And I'm singing.") >>> blob_fh.write(" And I'm singing.")
>>> blob_fh.close() >>> blob_fh.close()
>>> root5['blob'].open("rb").read() >>> root5['blob'].open("r").read()
"I'm a happy blob. And I'm singing." "I'm a happy blob. And I'm singing."
>>> savepoint = transaction.savepoint(optimistic=True) >>> savepoint = transaction.savepoint(optimistic=True)
>>> root5['blob'].open("rb").read() >>> root5['blob'].open("r").read()
"I'm a happy blob. And I'm singing." "I'm a happy blob. And I'm singing."
>>> transaction.get().commit() >>> transaction.get().commit()
...@@ -228,7 +228,7 @@ We do not support non-optimistic savepoints:: ...@@ -228,7 +228,7 @@ We do not support non-optimistic savepoints::
>>> blob_fh = root5['blob'].open("a") >>> blob_fh = root5['blob'].open("a")
>>> blob_fh.write(" And the weather is beautiful.") >>> blob_fh.write(" And the weather is beautiful.")
>>> blob_fh.close() >>> blob_fh.close()
>>> root5['blob'].open("rb").read() >>> root5['blob'].open("r").read()
"I'm a happy blob. And I'm singing. And the weather is beautiful." "I'm a happy blob. And I'm singing. And the weather is beautiful."
>>> savepoint = transaction.savepoint() # doctest: +ELLIPSIS >>> savepoint = transaction.savepoint() # doctest: +ELLIPSIS
Traceback (most recent call last): Traceback (most recent call last):
...@@ -245,7 +245,7 @@ stream a file to the browser), you can use the openDetached() method:: ...@@ -245,7 +245,7 @@ stream a file to the browser), you can use the openDetached() method::
>>> connection6 = database.open() >>> connection6 = database.open()
>>> root6 = connection6.root() >>> root6 = connection6.root()
>>> blob = Blob() >>> blob = Blob()
>>> blob_fh = blob.open("wb") >>> blob_fh = blob.open("w")
>>> blob_fh.write("I'm a happy blob.") >>> blob_fh.write("I'm a happy blob.")
>>> blob_fh.close() >>> blob_fh.close()
>>> root6['blob'] = blob >>> root6['blob'] = blob
...@@ -264,7 +264,7 @@ Of course, that doesn't work for empty blobs:: ...@@ -264,7 +264,7 @@ Of course, that doesn't work for empty blobs::
nor when the Blob is already opened for writing:: nor when the Blob is already opened for writing::
>>> blob = Blob() >>> blob = Blob()
>>> blob_fh = blob.open("wb") >>> blob_fh = blob.open("w")
>>> blob.openDetached() >>> blob.openDetached()
Traceback (most recent call last): Traceback (most recent call last):
... ...
...@@ -290,13 +290,13 @@ would be evil. ...@@ -290,13 +290,13 @@ would be evil.
It does work when the transaction was aborted, though:: It does work when the transaction was aborted, though::
>>> blob = Blob() >>> blob = Blob()
>>> blob_fh = blob.open("wb") >>> blob_fh = blob.open("w")
>>> blob_fh.write("I'm a happy blob.") >>> blob_fh.write("I'm a happy blob.")
>>> blob_fh.close() >>> blob_fh.close()
>>> root6['blob'] = blob >>> root6['blob'] = blob
>>> transaction.commit() >>> transaction.commit()
>>> blob_fh = blob.open("wb") >>> blob_fh = blob.open("w")
>>> blob_fh.write("And I'm singing.") >>> blob_fh.write("And I'm singing.")
>>> blob_fh.close() >>> blob_fh.close()
>>> transaction.abort() >>> transaction.abort()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment