1#!/usr/bin/env python
2"""Small helper class to provide a small slice of a stream.
3
4This class reads ahead to detect if we are at the end of the stream.
5"""
6
7from apitools.base.py import exceptions
8
9
10# TODO(user): Consider replacing this with a StringIO.
11class BufferedStream(object):
12
13    """Buffers a stream, reading ahead to determine if we're at the end."""
14
15    def __init__(self, stream, start, size):
16        self.__stream = stream
17        self.__start_pos = start
18        self.__buffer_pos = 0
19        self.__buffered_data = self.__stream.read(size)
20        self.__stream_at_end = len(self.__buffered_data) < size
21        self.__end_pos = self.__start_pos + len(self.__buffered_data)
22
23    def __str__(self):
24        return ('Buffered stream %s from position %s-%s with %s '
25                'bytes remaining' % (self.__stream, self.__start_pos,
26                                     self.__end_pos, self._bytes_remaining))
27
28    def __len__(self):
29        return len(self.__buffered_data)
30
31    @property
32    def stream_exhausted(self):
33        return self.__stream_at_end
34
35    @property
36    def stream_end_position(self):
37        return self.__end_pos
38
39    @property
40    def _bytes_remaining(self):
41        return len(self.__buffered_data) - self.__buffer_pos
42
43    def read(self, size=None):  # pylint: disable=invalid-name
44        """Reads from the buffer."""
45        if size is None or size < 0:
46            raise exceptions.NotYetImplementedError(
47                'Illegal read of size %s requested on BufferedStream. '
48                'Wrapped stream %s is at position %s-%s, '
49                '%s bytes remaining.' %
50                (size, self.__stream, self.__start_pos, self.__end_pos,
51                 self._bytes_remaining))
52
53        data = ''
54        if self._bytes_remaining:
55            size = min(size, self._bytes_remaining)
56            data = self.__buffered_data[
57                self.__buffer_pos:self.__buffer_pos + size]
58            self.__buffer_pos += size
59        return data
60