-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
SeekingLimitStream.php
189 lines (171 loc) · 5.5 KB
/
SeekingLimitStream.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
<?php
/**
* This file is part of the ZBateson\StreamDecorators project.
*
* @license http://opensource.org/licenses/bsd-license.php BSD
*/
namespace ZBateson\StreamDecorators;
use GuzzleHttp\Psr7\StreamDecoratorTrait;
use Psr\Http\Message\StreamInterface;
/**
* Maintains an internal 'read' position, and seeks to it before reading, then
* seeks back to the original position of the underlying stream after reading if
* the attached stream supports seeking.
*
* Although based on LimitStream, it's not inherited from it since $offset and
* $limit are set to private on LimitStream, and most other functions are re-
* implemented anyway. This also decouples the implementation from upstream
* changes.
*
* @author Zaahid Bateson
*/
class SeekingLimitStream implements StreamInterface
{
use StreamDecoratorTrait;
/** @var int Offset to start reading from */
private $offset;
/** @var int Limit the number of bytes that can be read */
private $limit;
/**
* @var int Number of bytes written, and importantly, if non-zero, writes a
* final $lineEnding on close (and so maintained instead of using
* tell() directly)
*/
private $position = 0;
/**
* @var StreamInterface $stream
*/
private $stream;
/**
* @param StreamInterface $stream Stream to wrap
* @param int $limit Total number of bytes to allow to be read
* from the stream. Pass -1 for no limit.
* @param int $offset Position to seek to before reading (only
* works on seekable streams).
*/
public function __construct(StreamInterface $stream, int $limit = -1, int $offset = 0)
{
$this->stream = $stream;
$this->setLimit($limit);
$this->setOffset($offset);
}
/**
* Returns the current relative read position of this stream subset.
*/
public function tell() : int
{
return $this->position;
}
/**
* Returns the size of the limited subset of data, or null if the wrapped
* stream returns null for getSize.
*/
public function getSize() : ?int
{
$size = $this->stream->getSize();
if ($size === null) {
// this shouldn't happen on a seekable stream I don't think...
$pos = $this->stream->tell();
$this->stream->seek(0, SEEK_END);
$size = $this->stream->tell();
$this->stream->seek($pos);
}
if ($this->limit === -1) {
return $size - $this->offset;
}
return \min([$this->limit, $size - $this->offset]);
}
/**
* Returns true if the current read position is at the end of the limited
* stream
*/
public function eof() : bool
{
$size = $this->limit;
if ($size === -1) {
$size = $this->getSize();
}
return ($this->position >= $size);
}
/**
* Ensures the seek position specified is within the stream's bounds, and
* sets the internal position pointer (doesn't actually seek).
*/
private function doSeek(int $pos) : void
{
if ($this->limit !== -1) {
$pos = \min([$pos, $this->limit]);
}
$this->position = \max([0, $pos]);
}
/**
* Seeks to the passed position within the confines of the limited stream's
* bounds.
*
* For SeekingLimitStream, no actual seek is performed on the underlying
* wrapped stream. Instead, an internal pointer is set, and the stream is
* 'seeked' on read operations
*/
public function seek(int $offset, int $whence = SEEK_SET) : void
{
$pos = $offset;
switch ($whence) {
case SEEK_CUR:
$pos = $this->position + $offset;
break;
case SEEK_END:
$pos = $this->limit + $offset;
break;
default:
break;
}
$this->doSeek($pos);
}
/**
* Sets the offset to start reading from the wrapped stream.
*/
public function setOffset(int $offset) : void
{
$this->offset = $offset;
$this->position = 0;
}
/**
* Sets the length of the stream to the passed $limit.
*/
public function setLimit(int $limit) : void
{
$this->limit = $limit;
}
/**
* Seeks to the current position and reads up to $length bytes, or less if
* it would result in reading past $this->limit
*/
public function seekAndRead(int $length) : string
{
$this->stream->seek($this->offset + $this->position);
if ($this->limit !== -1) {
$length = \min($length, $this->limit - $this->position);
if ($length <= 0) {
return '';
}
}
return $this->stream->read($length);
}
/**
* Reads from the underlying stream after seeking to the position within the
* bounds set for this limited stream. After reading, the wrapped stream is
* 'seeked' back to its position prior to the call to read().
*/
public function read(int $length) : string
{
$pos = $this->stream->tell();
$ret = $this->seekAndRead($length);
$this->position += \strlen($ret);
$this->stream->seek($pos);
if ($this->limit !== -1 && $this->position > $this->limit) {
$ret = \substr($ret, 0, -($this->position - $this->limit));
$this->position = $this->limit;
}
return $ret;
}
}