123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- ##############################################################################
- #
- # Copyright (c) 2001-2004 Zope Foundation 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.
- #
- ##############################################################################
- """Buffers
- """
- from io import BytesIO
- # copy_bytes controls the size of temp. strings for shuffling data around.
- COPY_BYTES = 1 << 18 # 256K
- # The maximum number of bytes to buffer in a simple string.
- STRBUF_LIMIT = 8192
- class FileBasedBuffer:
- remain = 0
- def __init__(self, file, from_buffer=None):
- self.file = file
- if from_buffer is not None:
- from_file = from_buffer.getfile()
- read_pos = from_file.tell()
- from_file.seek(0)
- while True:
- data = from_file.read(COPY_BYTES)
- if not data:
- break
- file.write(data)
- self.remain = int(file.tell() - read_pos)
- from_file.seek(read_pos)
- file.seek(read_pos)
- def __len__(self):
- return self.remain
- def __nonzero__(self):
- return True
- __bool__ = __nonzero__ # py3
- def append(self, s):
- file = self.file
- read_pos = file.tell()
- file.seek(0, 2)
- file.write(s)
- file.seek(read_pos)
- self.remain = self.remain + len(s)
- def get(self, numbytes=-1, skip=False):
- file = self.file
- if not skip:
- read_pos = file.tell()
- if numbytes < 0:
- # Read all
- res = file.read()
- else:
- res = file.read(numbytes)
- if skip:
- self.remain -= len(res)
- else:
- file.seek(read_pos)
- return res
- def skip(self, numbytes, allow_prune=0):
- if self.remain < numbytes:
- raise ValueError(
- "Can't skip %d bytes in buffer of %d bytes" % (numbytes, self.remain)
- )
- self.file.seek(numbytes, 1)
- self.remain = self.remain - numbytes
- def newfile(self):
- raise NotImplementedError()
- def prune(self):
- file = self.file
- if self.remain == 0:
- read_pos = file.tell()
- file.seek(0, 2)
- sz = file.tell()
- file.seek(read_pos)
- if sz == 0:
- # Nothing to prune.
- return
- nf = self.newfile()
- while True:
- data = file.read(COPY_BYTES)
- if not data:
- break
- nf.write(data)
- self.file = nf
- def getfile(self):
- return self.file
- def close(self):
- if hasattr(self.file, "close"):
- self.file.close()
- self.remain = 0
- class TempfileBasedBuffer(FileBasedBuffer):
- def __init__(self, from_buffer=None):
- FileBasedBuffer.__init__(self, self.newfile(), from_buffer)
- def newfile(self):
- from tempfile import TemporaryFile
- return TemporaryFile("w+b")
- class BytesIOBasedBuffer(FileBasedBuffer):
- def __init__(self, from_buffer=None):
- if from_buffer is not None:
- FileBasedBuffer.__init__(self, BytesIO(), from_buffer)
- else:
- # Shortcut. :-)
- self.file = BytesIO()
- def newfile(self):
- return BytesIO()
- def _is_seekable(fp):
- if hasattr(fp, "seekable"):
- return fp.seekable()
- return hasattr(fp, "seek") and hasattr(fp, "tell")
- class ReadOnlyFileBasedBuffer(FileBasedBuffer):
- # used as wsgi.file_wrapper
- def __init__(self, file, block_size=32768):
- self.file = file
- self.block_size = block_size # for __iter__
- # This is for the benefit of anyone that is attempting to wrap this
- # wsgi.file_wrapper in a WSGI middleware and wants to seek, this is
- # useful for instance for support Range requests
- if _is_seekable(self.file):
- if hasattr(self.file, "seekable"):
- self.seekable = self.file.seekable
- self.seek = self.file.seek
- self.tell = self.file.tell
- def prepare(self, size=None):
- if _is_seekable(self.file):
- start_pos = self.file.tell()
- self.file.seek(0, 2)
- end_pos = self.file.tell()
- self.file.seek(start_pos)
- fsize = end_pos - start_pos
- if size is None:
- self.remain = fsize
- else:
- self.remain = min(fsize, size)
- return self.remain
- def get(self, numbytes=-1, skip=False):
- # never read more than self.remain (it can be user-specified)
- if numbytes == -1 or numbytes > self.remain:
- numbytes = self.remain
- file = self.file
- if not skip:
- read_pos = file.tell()
- res = file.read(numbytes)
- if skip:
- self.remain -= len(res)
- else:
- file.seek(read_pos)
- return res
- def __iter__(self): # called by task if self.filelike has no seek/tell
- return self
- def next(self):
- val = self.file.read(self.block_size)
- if not val:
- raise StopIteration
- return val
- __next__ = next # py3
- def append(self, s):
- raise NotImplementedError
- class OverflowableBuffer:
- """
- This buffer implementation has four stages:
- - No data
- - Bytes-based buffer
- - BytesIO-based buffer
- - Temporary file storage
- The first two stages are fastest for simple transfers.
- """
- overflowed = False
- buf = None
- strbuf = b"" # Bytes-based buffer.
- def __init__(self, overflow):
- # overflow is the maximum to be stored in a StringIO buffer.
- self.overflow = overflow
- def __len__(self):
- buf = self.buf
- if buf is not None:
- # use buf.__len__ rather than len(buf) FBO of not getting
- # OverflowError on Python 2
- return buf.__len__()
- else:
- return self.strbuf.__len__()
- def __nonzero__(self):
- # use self.__len__ rather than len(self) FBO of not getting
- # OverflowError on Python 2
- return self.__len__() > 0
- __bool__ = __nonzero__ # py3
- def _create_buffer(self):
- strbuf = self.strbuf
- if len(strbuf) >= self.overflow:
- self._set_large_buffer()
- else:
- self._set_small_buffer()
- buf = self.buf
- if strbuf:
- buf.append(self.strbuf)
- self.strbuf = b""
- return buf
- def _set_small_buffer(self):
- oldbuf = self.buf
- self.buf = BytesIOBasedBuffer(oldbuf)
- # Attempt to close the old buffer
- if hasattr(oldbuf, "close"):
- oldbuf.close()
- self.overflowed = False
- def _set_large_buffer(self):
- oldbuf = self.buf
- self.buf = TempfileBasedBuffer(oldbuf)
- # Attempt to close the old buffer
- if hasattr(oldbuf, "close"):
- oldbuf.close()
- self.overflowed = True
- def append(self, s):
- buf = self.buf
- if buf is None:
- strbuf = self.strbuf
- if len(strbuf) + len(s) < STRBUF_LIMIT:
- self.strbuf = strbuf + s
- return
- buf = self._create_buffer()
- buf.append(s)
- # use buf.__len__ rather than len(buf) FBO of not getting
- # OverflowError on Python 2
- sz = buf.__len__()
- if not self.overflowed:
- if sz >= self.overflow:
- self._set_large_buffer()
- def get(self, numbytes=-1, skip=False):
- buf = self.buf
- if buf is None:
- strbuf = self.strbuf
- if not skip:
- return strbuf
- buf = self._create_buffer()
- return buf.get(numbytes, skip)
- def skip(self, numbytes, allow_prune=False):
- buf = self.buf
- if buf is None:
- if allow_prune and numbytes == len(self.strbuf):
- # We could slice instead of converting to
- # a buffer, but that would eat up memory in
- # large transfers.
- self.strbuf = b""
- return
- buf = self._create_buffer()
- buf.skip(numbytes, allow_prune)
- def prune(self):
- """
- A potentially expensive operation that removes all data
- already retrieved from the buffer.
- """
- buf = self.buf
- if buf is None:
- self.strbuf = b""
- return
- buf.prune()
- if self.overflowed:
- # use buf.__len__ rather than len(buf) FBO of not getting
- # OverflowError on Python 2
- sz = buf.__len__()
- if sz < self.overflow:
- # Revert to a faster buffer.
- self._set_small_buffer()
- def getfile(self):
- buf = self.buf
- if buf is None:
- buf = self._create_buffer()
- return buf.getfile()
- def close(self):
- buf = self.buf
- if buf is not None:
- buf.close()
|