| """Internal classes used by the gzip, lzma and bz2 modules""" | 
 |  | 
 | import io | 
 | import sys | 
 |  | 
 | BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE  # Compressed data read chunk size | 
 |  | 
 |  | 
 | class BaseStream(io.BufferedIOBase): | 
 |     """Mode-checking helper functions.""" | 
 |  | 
 |     def _check_not_closed(self): | 
 |         if self.closed: | 
 |             raise ValueError("I/O operation on closed file") | 
 |  | 
 |     def _check_can_read(self): | 
 |         if not self.readable(): | 
 |             raise io.UnsupportedOperation("File not open for reading") | 
 |  | 
 |     def _check_can_write(self): | 
 |         if not self.writable(): | 
 |             raise io.UnsupportedOperation("File not open for writing") | 
 |  | 
 |     def _check_can_seek(self): | 
 |         if not self.readable(): | 
 |             raise io.UnsupportedOperation("Seeking is only supported " | 
 |                                           "on files open for reading") | 
 |         if not self.seekable(): | 
 |             raise io.UnsupportedOperation("The underlying file object " | 
 |                                           "does not support seeking") | 
 |  | 
 |  | 
 | class DecompressReader(io.RawIOBase): | 
 |     """Adapts the decompressor API to a RawIOBase reader API""" | 
 |  | 
 |     def readable(self): | 
 |         return True | 
 |  | 
 |     def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args): | 
 |         self._fp = fp | 
 |         self._eof = False | 
 |         self._pos = 0  # Current offset in decompressed stream | 
 |  | 
 |         # Set to size of decompressed stream once it is known, for SEEK_END | 
 |         self._size = -1 | 
 |  | 
 |         # Save the decompressor factory and arguments. | 
 |         # If the file contains multiple compressed streams, each | 
 |         # stream will need a separate decompressor object. A new decompressor | 
 |         # object is also needed when implementing a backwards seek(). | 
 |         self._decomp_factory = decomp_factory | 
 |         self._decomp_args = decomp_args | 
 |         self._decompressor = self._decomp_factory(**self._decomp_args) | 
 |  | 
 |         # Exception class to catch from decompressor signifying invalid | 
 |         # trailing data to ignore | 
 |         self._trailing_error = trailing_error | 
 |  | 
 |     def close(self): | 
 |         self._decompressor = None | 
 |         return super().close() | 
 |  | 
 |     def seekable(self): | 
 |         return self._fp.seekable() | 
 |  | 
 |     def readinto(self, b): | 
 |         with memoryview(b) as view, view.cast("B") as byte_view: | 
 |             data = self.read(len(byte_view)) | 
 |             byte_view[:len(data)] = data | 
 |         return len(data) | 
 |  | 
 |     def read(self, size=-1): | 
 |         if size < 0: | 
 |             return self.readall() | 
 |  | 
 |         if not size or self._eof: | 
 |             return b"" | 
 |         data = None  # Default if EOF is encountered | 
 |         # Depending on the input data, our call to the decompressor may not | 
 |         # return any data. In this case, try again after reading another block. | 
 |         while True: | 
 |             if self._decompressor.eof: | 
 |                 rawblock = (self._decompressor.unused_data or | 
 |                             self._fp.read(BUFFER_SIZE)) | 
 |                 if not rawblock: | 
 |                     break | 
 |                 # Continue to next stream. | 
 |                 self._decompressor = self._decomp_factory( | 
 |                     **self._decomp_args) | 
 |                 try: | 
 |                     data = self._decompressor.decompress(rawblock, size) | 
 |                 except self._trailing_error: | 
 |                     # Trailing data isn't a valid compressed stream; ignore it. | 
 |                     break | 
 |             else: | 
 |                 if self._decompressor.needs_input: | 
 |                     rawblock = self._fp.read(BUFFER_SIZE) | 
 |                     if not rawblock: | 
 |                         raise EOFError("Compressed file ended before the " | 
 |                                        "end-of-stream marker was reached") | 
 |                 else: | 
 |                     rawblock = b"" | 
 |                 data = self._decompressor.decompress(rawblock, size) | 
 |             if data: | 
 |                 break | 
 |         if not data: | 
 |             self._eof = True | 
 |             self._size = self._pos | 
 |             return b"" | 
 |         self._pos += len(data) | 
 |         return data | 
 |  | 
 |     def readall(self): | 
 |         chunks = [] | 
 |         # sys.maxsize means the max length of output buffer is unlimited, | 
 |         # so that the whole input buffer can be decompressed within one | 
 |         # .decompress() call. | 
 |         while data := self.read(sys.maxsize): | 
 |             chunks.append(data) | 
 |  | 
 |         return b"".join(chunks) | 
 |  | 
 |     # Rewind the file to the beginning of the data stream. | 
 |     def _rewind(self): | 
 |         self._fp.seek(0) | 
 |         self._eof = False | 
 |         self._pos = 0 | 
 |         self._decompressor = self._decomp_factory(**self._decomp_args) | 
 |  | 
 |     def seek(self, offset, whence=io.SEEK_SET): | 
 |         # Recalculate offset as an absolute file position. | 
 |         if whence == io.SEEK_SET: | 
 |             pass | 
 |         elif whence == io.SEEK_CUR: | 
 |             offset = self._pos + offset | 
 |         elif whence == io.SEEK_END: | 
 |             # Seeking relative to EOF - we need to know the file's size. | 
 |             if self._size < 0: | 
 |                 while self.read(io.DEFAULT_BUFFER_SIZE): | 
 |                     pass | 
 |             offset = self._size + offset | 
 |         else: | 
 |             raise ValueError("Invalid value for whence: {}".format(whence)) | 
 |  | 
 |         # Make it so that offset is the number of bytes to skip forward. | 
 |         if offset < self._pos: | 
 |             self._rewind() | 
 |         else: | 
 |             offset -= self._pos | 
 |  | 
 |         # Read and discard data until we reach the desired position. | 
 |         while offset > 0: | 
 |             data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset)) | 
 |             if not data: | 
 |                 break | 
 |             offset -= len(data) | 
 |  | 
 |         return self._pos | 
 |  | 
 |     def tell(self): | 
 |         """Return the current file position.""" | 
 |         return self._pos |