xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/_compression.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""Internal classes used by the gzip, lzma and bz2 modules"""
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard Workerimport io
4*cda5da8dSAndroid Build Coastguard Workerimport sys
5*cda5da8dSAndroid Build Coastguard Worker
6*cda5da8dSAndroid Build Coastguard WorkerBUFFER_SIZE = io.DEFAULT_BUFFER_SIZE  # Compressed data read chunk size
7*cda5da8dSAndroid Build Coastguard Worker
8*cda5da8dSAndroid Build Coastguard Worker
9*cda5da8dSAndroid Build Coastguard Workerclass BaseStream(io.BufferedIOBase):
10*cda5da8dSAndroid Build Coastguard Worker    """Mode-checking helper functions."""
11*cda5da8dSAndroid Build Coastguard Worker
12*cda5da8dSAndroid Build Coastguard Worker    def _check_not_closed(self):
13*cda5da8dSAndroid Build Coastguard Worker        if self.closed:
14*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("I/O operation on closed file")
15*cda5da8dSAndroid Build Coastguard Worker
16*cda5da8dSAndroid Build Coastguard Worker    def _check_can_read(self):
17*cda5da8dSAndroid Build Coastguard Worker        if not self.readable():
18*cda5da8dSAndroid Build Coastguard Worker            raise io.UnsupportedOperation("File not open for reading")
19*cda5da8dSAndroid Build Coastguard Worker
20*cda5da8dSAndroid Build Coastguard Worker    def _check_can_write(self):
21*cda5da8dSAndroid Build Coastguard Worker        if not self.writable():
22*cda5da8dSAndroid Build Coastguard Worker            raise io.UnsupportedOperation("File not open for writing")
23*cda5da8dSAndroid Build Coastguard Worker
24*cda5da8dSAndroid Build Coastguard Worker    def _check_can_seek(self):
25*cda5da8dSAndroid Build Coastguard Worker        if not self.readable():
26*cda5da8dSAndroid Build Coastguard Worker            raise io.UnsupportedOperation("Seeking is only supported "
27*cda5da8dSAndroid Build Coastguard Worker                                          "on files open for reading")
28*cda5da8dSAndroid Build Coastguard Worker        if not self.seekable():
29*cda5da8dSAndroid Build Coastguard Worker            raise io.UnsupportedOperation("The underlying file object "
30*cda5da8dSAndroid Build Coastguard Worker                                          "does not support seeking")
31*cda5da8dSAndroid Build Coastguard Worker
32*cda5da8dSAndroid Build Coastguard Worker
33*cda5da8dSAndroid Build Coastguard Workerclass DecompressReader(io.RawIOBase):
34*cda5da8dSAndroid Build Coastguard Worker    """Adapts the decompressor API to a RawIOBase reader API"""
35*cda5da8dSAndroid Build Coastguard Worker
36*cda5da8dSAndroid Build Coastguard Worker    def readable(self):
37*cda5da8dSAndroid Build Coastguard Worker        return True
38*cda5da8dSAndroid Build Coastguard Worker
39*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args):
40*cda5da8dSAndroid Build Coastguard Worker        self._fp = fp
41*cda5da8dSAndroid Build Coastguard Worker        self._eof = False
42*cda5da8dSAndroid Build Coastguard Worker        self._pos = 0  # Current offset in decompressed stream
43*cda5da8dSAndroid Build Coastguard Worker
44*cda5da8dSAndroid Build Coastguard Worker        # Set to size of decompressed stream once it is known, for SEEK_END
45*cda5da8dSAndroid Build Coastguard Worker        self._size = -1
46*cda5da8dSAndroid Build Coastguard Worker
47*cda5da8dSAndroid Build Coastguard Worker        # Save the decompressor factory and arguments.
48*cda5da8dSAndroid Build Coastguard Worker        # If the file contains multiple compressed streams, each
49*cda5da8dSAndroid Build Coastguard Worker        # stream will need a separate decompressor object. A new decompressor
50*cda5da8dSAndroid Build Coastguard Worker        # object is also needed when implementing a backwards seek().
51*cda5da8dSAndroid Build Coastguard Worker        self._decomp_factory = decomp_factory
52*cda5da8dSAndroid Build Coastguard Worker        self._decomp_args = decomp_args
53*cda5da8dSAndroid Build Coastguard Worker        self._decompressor = self._decomp_factory(**self._decomp_args)
54*cda5da8dSAndroid Build Coastguard Worker
55*cda5da8dSAndroid Build Coastguard Worker        # Exception class to catch from decompressor signifying invalid
56*cda5da8dSAndroid Build Coastguard Worker        # trailing data to ignore
57*cda5da8dSAndroid Build Coastguard Worker        self._trailing_error = trailing_error
58*cda5da8dSAndroid Build Coastguard Worker
59*cda5da8dSAndroid Build Coastguard Worker    def close(self):
60*cda5da8dSAndroid Build Coastguard Worker        self._decompressor = None
61*cda5da8dSAndroid Build Coastguard Worker        return super().close()
62*cda5da8dSAndroid Build Coastguard Worker
63*cda5da8dSAndroid Build Coastguard Worker    def seekable(self):
64*cda5da8dSAndroid Build Coastguard Worker        return self._fp.seekable()
65*cda5da8dSAndroid Build Coastguard Worker
66*cda5da8dSAndroid Build Coastguard Worker    def readinto(self, b):
67*cda5da8dSAndroid Build Coastguard Worker        with memoryview(b) as view, view.cast("B") as byte_view:
68*cda5da8dSAndroid Build Coastguard Worker            data = self.read(len(byte_view))
69*cda5da8dSAndroid Build Coastguard Worker            byte_view[:len(data)] = data
70*cda5da8dSAndroid Build Coastguard Worker        return len(data)
71*cda5da8dSAndroid Build Coastguard Worker
72*cda5da8dSAndroid Build Coastguard Worker    def read(self, size=-1):
73*cda5da8dSAndroid Build Coastguard Worker        if size < 0:
74*cda5da8dSAndroid Build Coastguard Worker            return self.readall()
75*cda5da8dSAndroid Build Coastguard Worker
76*cda5da8dSAndroid Build Coastguard Worker        if not size or self._eof:
77*cda5da8dSAndroid Build Coastguard Worker            return b""
78*cda5da8dSAndroid Build Coastguard Worker        data = None  # Default if EOF is encountered
79*cda5da8dSAndroid Build Coastguard Worker        # Depending on the input data, our call to the decompressor may not
80*cda5da8dSAndroid Build Coastguard Worker        # return any data. In this case, try again after reading another block.
81*cda5da8dSAndroid Build Coastguard Worker        while True:
82*cda5da8dSAndroid Build Coastguard Worker            if self._decompressor.eof:
83*cda5da8dSAndroid Build Coastguard Worker                rawblock = (self._decompressor.unused_data or
84*cda5da8dSAndroid Build Coastguard Worker                            self._fp.read(BUFFER_SIZE))
85*cda5da8dSAndroid Build Coastguard Worker                if not rawblock:
86*cda5da8dSAndroid Build Coastguard Worker                    break
87*cda5da8dSAndroid Build Coastguard Worker                # Continue to next stream.
88*cda5da8dSAndroid Build Coastguard Worker                self._decompressor = self._decomp_factory(
89*cda5da8dSAndroid Build Coastguard Worker                    **self._decomp_args)
90*cda5da8dSAndroid Build Coastguard Worker                try:
91*cda5da8dSAndroid Build Coastguard Worker                    data = self._decompressor.decompress(rawblock, size)
92*cda5da8dSAndroid Build Coastguard Worker                except self._trailing_error:
93*cda5da8dSAndroid Build Coastguard Worker                    # Trailing data isn't a valid compressed stream; ignore it.
94*cda5da8dSAndroid Build Coastguard Worker                    break
95*cda5da8dSAndroid Build Coastguard Worker            else:
96*cda5da8dSAndroid Build Coastguard Worker                if self._decompressor.needs_input:
97*cda5da8dSAndroid Build Coastguard Worker                    rawblock = self._fp.read(BUFFER_SIZE)
98*cda5da8dSAndroid Build Coastguard Worker                    if not rawblock:
99*cda5da8dSAndroid Build Coastguard Worker                        raise EOFError("Compressed file ended before the "
100*cda5da8dSAndroid Build Coastguard Worker                                       "end-of-stream marker was reached")
101*cda5da8dSAndroid Build Coastguard Worker                else:
102*cda5da8dSAndroid Build Coastguard Worker                    rawblock = b""
103*cda5da8dSAndroid Build Coastguard Worker                data = self._decompressor.decompress(rawblock, size)
104*cda5da8dSAndroid Build Coastguard Worker            if data:
105*cda5da8dSAndroid Build Coastguard Worker                break
106*cda5da8dSAndroid Build Coastguard Worker        if not data:
107*cda5da8dSAndroid Build Coastguard Worker            self._eof = True
108*cda5da8dSAndroid Build Coastguard Worker            self._size = self._pos
109*cda5da8dSAndroid Build Coastguard Worker            return b""
110*cda5da8dSAndroid Build Coastguard Worker        self._pos += len(data)
111*cda5da8dSAndroid Build Coastguard Worker        return data
112*cda5da8dSAndroid Build Coastguard Worker
113*cda5da8dSAndroid Build Coastguard Worker    def readall(self):
114*cda5da8dSAndroid Build Coastguard Worker        chunks = []
115*cda5da8dSAndroid Build Coastguard Worker        # sys.maxsize means the max length of output buffer is unlimited,
116*cda5da8dSAndroid Build Coastguard Worker        # so that the whole input buffer can be decompressed within one
117*cda5da8dSAndroid Build Coastguard Worker        # .decompress() call.
118*cda5da8dSAndroid Build Coastguard Worker        while data := self.read(sys.maxsize):
119*cda5da8dSAndroid Build Coastguard Worker            chunks.append(data)
120*cda5da8dSAndroid Build Coastguard Worker
121*cda5da8dSAndroid Build Coastguard Worker        return b"".join(chunks)
122*cda5da8dSAndroid Build Coastguard Worker
123*cda5da8dSAndroid Build Coastguard Worker    # Rewind the file to the beginning of the data stream.
124*cda5da8dSAndroid Build Coastguard Worker    def _rewind(self):
125*cda5da8dSAndroid Build Coastguard Worker        self._fp.seek(0)
126*cda5da8dSAndroid Build Coastguard Worker        self._eof = False
127*cda5da8dSAndroid Build Coastguard Worker        self._pos = 0
128*cda5da8dSAndroid Build Coastguard Worker        self._decompressor = self._decomp_factory(**self._decomp_args)
129*cda5da8dSAndroid Build Coastguard Worker
130*cda5da8dSAndroid Build Coastguard Worker    def seek(self, offset, whence=io.SEEK_SET):
131*cda5da8dSAndroid Build Coastguard Worker        # Recalculate offset as an absolute file position.
132*cda5da8dSAndroid Build Coastguard Worker        if whence == io.SEEK_SET:
133*cda5da8dSAndroid Build Coastguard Worker            pass
134*cda5da8dSAndroid Build Coastguard Worker        elif whence == io.SEEK_CUR:
135*cda5da8dSAndroid Build Coastguard Worker            offset = self._pos + offset
136*cda5da8dSAndroid Build Coastguard Worker        elif whence == io.SEEK_END:
137*cda5da8dSAndroid Build Coastguard Worker            # Seeking relative to EOF - we need to know the file's size.
138*cda5da8dSAndroid Build Coastguard Worker            if self._size < 0:
139*cda5da8dSAndroid Build Coastguard Worker                while self.read(io.DEFAULT_BUFFER_SIZE):
140*cda5da8dSAndroid Build Coastguard Worker                    pass
141*cda5da8dSAndroid Build Coastguard Worker            offset = self._size + offset
142*cda5da8dSAndroid Build Coastguard Worker        else:
143*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("Invalid value for whence: {}".format(whence))
144*cda5da8dSAndroid Build Coastguard Worker
145*cda5da8dSAndroid Build Coastguard Worker        # Make it so that offset is the number of bytes to skip forward.
146*cda5da8dSAndroid Build Coastguard Worker        if offset < self._pos:
147*cda5da8dSAndroid Build Coastguard Worker            self._rewind()
148*cda5da8dSAndroid Build Coastguard Worker        else:
149*cda5da8dSAndroid Build Coastguard Worker            offset -= self._pos
150*cda5da8dSAndroid Build Coastguard Worker
151*cda5da8dSAndroid Build Coastguard Worker        # Read and discard data until we reach the desired position.
152*cda5da8dSAndroid Build Coastguard Worker        while offset > 0:
153*cda5da8dSAndroid Build Coastguard Worker            data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset))
154*cda5da8dSAndroid Build Coastguard Worker            if not data:
155*cda5da8dSAndroid Build Coastguard Worker                break
156*cda5da8dSAndroid Build Coastguard Worker            offset -= len(data)
157*cda5da8dSAndroid Build Coastguard Worker
158*cda5da8dSAndroid Build Coastguard Worker        return self._pos
159*cda5da8dSAndroid Build Coastguard Worker
160*cda5da8dSAndroid Build Coastguard Worker    def tell(self):
161*cda5da8dSAndroid Build Coastguard Worker        """Return the current file position."""
162*cda5da8dSAndroid Build Coastguard Worker        return self._pos
163