3232class SeekableHttpStream implements File {
3333 private const PROTOCOL = 'httpseek';
3434
35- private static $registered = false;
35+ private static bool $registered = false;
3636
3737 /**
3838 * Registers the stream wrapper using the `httpseek://` url scheme
@@ -73,24 +73,26 @@ public static function open(callable $callback) {
7373 /** @var callable */
7474 private $openCallback;
7575
76- /** @var resource */
76+ /** @var ?resource|closed- resource */
7777 private $current;
78- /** @var int */
79- private $offset = 0;
80- /** @var int */
81- private $length = 0;
78+ private int $offset = 0;
79+ private int $length = 0;
80+ private bool $needReconnect = false;
8281
83- private function reconnect(int $start) {
82+ private function reconnect(int $start): bool {
83+ $this->needReconnect = false;
8484 $range = $start . '-';
85- if ($this->current != null ) {
85+ if ($this->hasOpenStream() ) {
8686 fclose($this->current);
8787 }
8888
89- $this->current = ($this->openCallback)($range);
89+ $stream = ($this->openCallback)($range);
9090
9191 if ($this->current === false) {
92+ $this->current = null;
9293 return false;
9394 }
95+ $this->current = $stream;
9496
9597 $responseHead = stream_get_meta_data($this->current)['wrapper_data'];
9698
@@ -109,6 +111,7 @@ private function reconnect(int $start) {
109111 return preg_match('#^content-range:#i', $v) === 1;
110112 }));
111113 if (!$rangeHeaders) {
114+ $this->current = null;
112115 return false;
113116 }
114117 $contentRange = $rangeHeaders[0];
@@ -119,6 +122,7 @@ private function reconnect(int $start) {
119122 $length = intval(explode('/', $range)[1]);
120123
121124 if ($begin !== $start) {
125+ $this->current = null;
122126 return false;
123127 }
124128
@@ -128,6 +132,28 @@ private function reconnect(int $start) {
128132 return true;
129133 }
130134
135+ /**
136+ * @return ?resource
137+ */
138+ private function getCurrent() {
139+ if ($this->needReconnect) {
140+ $this->reconnect($this->offset);
141+ }
142+ if (is_resource($this->current)) {
143+ return $this->current;
144+ } else {
145+ return null;
146+ }
147+ }
148+
149+ /**
150+ * @return bool
151+ * @psalm-assert-if-true resource $this->current
152+ */
153+ private function hasOpenStream(): bool {
154+ return is_resource($this->current);
155+ }
156+
131157 public function stream_open($path, $mode, $options, &$opened_path) {
132158 $options = stream_context_get_options($this->context)[self::PROTOCOL];
133159 $this->openCallback = $options['callback'];
@@ -136,10 +162,10 @@ public function stream_open($path, $mode, $options, &$opened_path) {
136162 }
137163
138164 public function stream_read($count) {
139- if (!$this->current ) {
165+ if (!$this->getCurrent() ) {
140166 return false;
141167 }
142- $ret = fread($this->current , $count);
168+ $ret = fread($this->getCurrent() , $count);
143169 $this->offset += strlen($ret);
144170 return $ret;
145171 }
@@ -149,48 +175,61 @@ public function stream_seek($offset, $whence = SEEK_SET) {
149175 case SEEK_SET:
150176 if ($offset === $this->offset) {
151177 return true;
178+ } else {
179+ $this->offset = $offset;
180+ break;
152181 }
153- return $this->reconnect($offset);
154182 case SEEK_CUR:
155183 if ($offset === 0) {
156184 return true;
185+ } else {
186+ $this->offset += $offset;
187+ break;
157188 }
158- return $this->reconnect($this->offset + $offset);
159189 case SEEK_END:
160190 if ($this->length === 0) {
161191 return false;
162192 } elseif ($this->length + $offset === $this->offset) {
163193 return true;
194+ } else {
195+ $this->offset = $this->length + $offset;
196+ break;
164197 }
165- return $this->reconnect($this->length + $offset);
166198 }
167- return false;
199+
200+ if ($this->hasOpenStream()) {
201+ fclose($this->current);
202+ }
203+ $this->current = null;
204+ $this->needReconnect = true;
205+ return true;
168206 }
169207
170208 public function stream_tell() {
171209 return $this->offset;
172210 }
173211
174212 public function stream_stat() {
175- if (is_resource( $this->current )) {
176- return fstat($this->current );
213+ if ($this->getCurrent( )) {
214+ return fstat($this->getCurrent() );
177215 } else {
178216 return false;
179217 }
180218 }
181219
182220 public function stream_eof() {
183- if (is_resource( $this->current )) {
184- return feof($this->current );
221+ if ($this->getCurrent( )) {
222+ return feof($this->getCurrent() );
185223 } else {
186224 return true;
187225 }
188226 }
189227
190228 public function stream_close() {
191- if (is_resource( $this->current )) {
229+ if ($this->hasOpenStream( )) {
192230 fclose($this->current);
193231 }
232+ $this->current = null;
194233 }
195234
196235 public function stream_write($data) {
0 commit comments