Chris@0
|
1 <?php
|
Chris@0
|
2 namespace GuzzleHttp\Psr7;
|
Chris@0
|
3
|
Chris@0
|
4 use Psr\Http\Message\StreamInterface;
|
Chris@0
|
5
|
Chris@0
|
6
|
Chris@0
|
7 /**
|
Chris@0
|
8 * Decorator used to return only a subset of a stream
|
Chris@0
|
9 */
|
Chris@0
|
10 class LimitStream implements StreamInterface
|
Chris@0
|
11 {
|
Chris@0
|
12 use StreamDecoratorTrait;
|
Chris@0
|
13
|
Chris@0
|
14 /** @var int Offset to start reading from */
|
Chris@0
|
15 private $offset;
|
Chris@0
|
16
|
Chris@0
|
17 /** @var int Limit the number of bytes that can be read */
|
Chris@0
|
18 private $limit;
|
Chris@0
|
19
|
Chris@0
|
20 /**
|
Chris@0
|
21 * @param StreamInterface $stream Stream to wrap
|
Chris@0
|
22 * @param int $limit Total number of bytes to allow to be read
|
Chris@0
|
23 * from the stream. Pass -1 for no limit.
|
Chris@0
|
24 * @param int $offset Position to seek to before reading (only
|
Chris@0
|
25 * works on seekable streams).
|
Chris@0
|
26 */
|
Chris@0
|
27 public function __construct(
|
Chris@0
|
28 StreamInterface $stream,
|
Chris@0
|
29 $limit = -1,
|
Chris@0
|
30 $offset = 0
|
Chris@0
|
31 ) {
|
Chris@0
|
32 $this->stream = $stream;
|
Chris@0
|
33 $this->setLimit($limit);
|
Chris@0
|
34 $this->setOffset($offset);
|
Chris@0
|
35 }
|
Chris@0
|
36
|
Chris@0
|
37 public function eof()
|
Chris@0
|
38 {
|
Chris@0
|
39 // Always return true if the underlying stream is EOF
|
Chris@0
|
40 if ($this->stream->eof()) {
|
Chris@0
|
41 return true;
|
Chris@0
|
42 }
|
Chris@0
|
43
|
Chris@0
|
44 // No limit and the underlying stream is not at EOF
|
Chris@0
|
45 if ($this->limit == -1) {
|
Chris@0
|
46 return false;
|
Chris@0
|
47 }
|
Chris@0
|
48
|
Chris@0
|
49 return $this->stream->tell() >= $this->offset + $this->limit;
|
Chris@0
|
50 }
|
Chris@0
|
51
|
Chris@0
|
52 /**
|
Chris@0
|
53 * Returns the size of the limited subset of data
|
Chris@0
|
54 * {@inheritdoc}
|
Chris@0
|
55 */
|
Chris@0
|
56 public function getSize()
|
Chris@0
|
57 {
|
Chris@0
|
58 if (null === ($length = $this->stream->getSize())) {
|
Chris@0
|
59 return null;
|
Chris@0
|
60 } elseif ($this->limit == -1) {
|
Chris@0
|
61 return $length - $this->offset;
|
Chris@0
|
62 } else {
|
Chris@0
|
63 return min($this->limit, $length - $this->offset);
|
Chris@0
|
64 }
|
Chris@0
|
65 }
|
Chris@0
|
66
|
Chris@0
|
67 /**
|
Chris@0
|
68 * Allow for a bounded seek on the read limited stream
|
Chris@0
|
69 * {@inheritdoc}
|
Chris@0
|
70 */
|
Chris@0
|
71 public function seek($offset, $whence = SEEK_SET)
|
Chris@0
|
72 {
|
Chris@0
|
73 if ($whence !== SEEK_SET || $offset < 0) {
|
Chris@0
|
74 throw new \RuntimeException(sprintf(
|
Chris@0
|
75 'Cannot seek to offset % with whence %s',
|
Chris@0
|
76 $offset,
|
Chris@0
|
77 $whence
|
Chris@0
|
78 ));
|
Chris@0
|
79 }
|
Chris@0
|
80
|
Chris@0
|
81 $offset += $this->offset;
|
Chris@0
|
82
|
Chris@0
|
83 if ($this->limit !== -1) {
|
Chris@0
|
84 if ($offset > $this->offset + $this->limit) {
|
Chris@0
|
85 $offset = $this->offset + $this->limit;
|
Chris@0
|
86 }
|
Chris@0
|
87 }
|
Chris@0
|
88
|
Chris@0
|
89 $this->stream->seek($offset);
|
Chris@0
|
90 }
|
Chris@0
|
91
|
Chris@0
|
92 /**
|
Chris@0
|
93 * Give a relative tell()
|
Chris@0
|
94 * {@inheritdoc}
|
Chris@0
|
95 */
|
Chris@0
|
96 public function tell()
|
Chris@0
|
97 {
|
Chris@0
|
98 return $this->stream->tell() - $this->offset;
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 /**
|
Chris@0
|
102 * Set the offset to start limiting from
|
Chris@0
|
103 *
|
Chris@0
|
104 * @param int $offset Offset to seek to and begin byte limiting from
|
Chris@0
|
105 *
|
Chris@0
|
106 * @throws \RuntimeException if the stream cannot be seeked.
|
Chris@0
|
107 */
|
Chris@0
|
108 public function setOffset($offset)
|
Chris@0
|
109 {
|
Chris@0
|
110 $current = $this->stream->tell();
|
Chris@0
|
111
|
Chris@0
|
112 if ($current !== $offset) {
|
Chris@0
|
113 // If the stream cannot seek to the offset position, then read to it
|
Chris@0
|
114 if ($this->stream->isSeekable()) {
|
Chris@0
|
115 $this->stream->seek($offset);
|
Chris@0
|
116 } elseif ($current > $offset) {
|
Chris@0
|
117 throw new \RuntimeException("Could not seek to stream offset $offset");
|
Chris@0
|
118 } else {
|
Chris@0
|
119 $this->stream->read($offset - $current);
|
Chris@0
|
120 }
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 $this->offset = $offset;
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 /**
|
Chris@0
|
127 * Set the limit of bytes that the decorator allows to be read from the
|
Chris@0
|
128 * stream.
|
Chris@0
|
129 *
|
Chris@0
|
130 * @param int $limit Number of bytes to allow to be read from the stream.
|
Chris@0
|
131 * Use -1 for no limit.
|
Chris@0
|
132 */
|
Chris@0
|
133 public function setLimit($limit)
|
Chris@0
|
134 {
|
Chris@0
|
135 $this->limit = $limit;
|
Chris@0
|
136 }
|
Chris@0
|
137
|
Chris@0
|
138 public function read($length)
|
Chris@0
|
139 {
|
Chris@0
|
140 if ($this->limit == -1) {
|
Chris@0
|
141 return $this->stream->read($length);
|
Chris@0
|
142 }
|
Chris@0
|
143
|
Chris@0
|
144 // Check if the current position is less than the total allowed
|
Chris@0
|
145 // bytes + original offset
|
Chris@0
|
146 $remaining = ($this->offset + $this->limit) - $this->stream->tell();
|
Chris@0
|
147 if ($remaining > 0) {
|
Chris@0
|
148 // Only return the amount of requested data, ensuring that the byte
|
Chris@0
|
149 // limit is not exceeded
|
Chris@0
|
150 return $this->stream->read(min($remaining, $length));
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@0
|
153 return '';
|
Chris@0
|
154 }
|
Chris@0
|
155 }
|