32
32
class SeekableHttpStream implements File {
33
33
private const PROTOCOL = 'httpseek ' ;
34
34
35
- private static $ registered = false ;
35
+ private static bool $ registered = false ;
36
36
37
37
/**
38
38
* Registers the stream wrapper using the `httpseek://` url scheme
@@ -73,24 +73,26 @@ public static function open(callable $callback) {
73
73
/** @var callable */
74
74
private $ openCallback ;
75
75
76
- /** @var resource */
76
+ /** @var ?resource|closed- resource */
77
77
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 ;
82
81
83
- private function reconnect (int $ start ) {
82
+ private function reconnect (int $ start ): bool {
83
+ $ this ->needReconnect = false ;
84
84
$ range = $ start . '- ' ;
85
- if ($ this ->current != null ) {
85
+ if ($ this ->hasOpenStream () ) {
86
86
fclose ($ this ->current );
87
87
}
88
88
89
- $ this -> current = ($ this ->openCallback )($ range );
89
+ $ stream = ($ this ->openCallback )($ range );
90
90
91
- if ($ this ->current === false ) {
91
+ if ($ stream === false ) {
92
+ $ this ->current = null ;
92
93
return false ;
93
94
}
95
+ $ this ->current = $ stream ;
94
96
95
97
$ responseHead = stream_get_meta_data ($ this ->current )['wrapper_data ' ];
96
98
@@ -109,6 +111,7 @@ private function reconnect(int $start) {
109
111
return preg_match ('#^content-range:#i ' , $ v ) === 1 ;
110
112
}));
111
113
if (!$ rangeHeaders ) {
114
+ $ this ->current = null ;
112
115
return false ;
113
116
}
114
117
$ contentRange = $ rangeHeaders [0 ];
@@ -119,6 +122,7 @@ private function reconnect(int $start) {
119
122
$ length = intval (explode ('/ ' , $ range )[1 ]);
120
123
121
124
if ($ begin !== $ start ) {
125
+ $ this ->current = null ;
122
126
return false ;
123
127
}
124
128
@@ -128,6 +132,28 @@ private function reconnect(int $start) {
128
132
return true ;
129
133
}
130
134
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
+
131
157
public function stream_open ($ path , $ mode , $ options , &$ opened_path ) {
132
158
$ options = stream_context_get_options ($ this ->context )[self ::PROTOCOL ];
133
159
$ this ->openCallback = $ options ['callback ' ];
@@ -136,10 +162,10 @@ public function stream_open($path, $mode, $options, &$opened_path) {
136
162
}
137
163
138
164
public function stream_read ($ count ) {
139
- if (!$ this ->current ) {
165
+ if (!$ this ->getCurrent () ) {
140
166
return false ;
141
167
}
142
- $ ret = fread ($ this ->current , $ count );
168
+ $ ret = fread ($ this ->getCurrent () , $ count );
143
169
$ this ->offset += strlen ($ ret );
144
170
return $ ret ;
145
171
}
@@ -149,48 +175,61 @@ public function stream_seek($offset, $whence = SEEK_SET) {
149
175
case SEEK_SET :
150
176
if ($ offset === $ this ->offset ) {
151
177
return true ;
178
+ } else {
179
+ $ this ->offset = $ offset ;
152
180
}
153
- return $ this -> reconnect ( $ offset ) ;
181
+ break ;
154
182
case SEEK_CUR :
155
183
if ($ offset === 0 ) {
156
184
return true ;
185
+ } else {
186
+ $ this ->offset += $ offset ;
157
187
}
158
- return $ this -> reconnect ( $ this -> offset + $ offset ) ;
188
+ break ;
159
189
case SEEK_END :
160
190
if ($ this ->length === 0 ) {
161
191
return false ;
162
192
} elseif ($ this ->length + $ offset === $ this ->offset ) {
163
193
return true ;
194
+ } else {
195
+ $ this ->offset = $ this ->length + $ offset ;
164
196
}
165
- return $ this -> reconnect ( $ this -> length + $ offset ) ;
197
+ break ;
166
198
}
167
- return false ;
199
+
200
+ if ($ this ->hasOpenStream ()) {
201
+ fclose ($ this ->current );
202
+ }
203
+ $ this ->current = null ;
204
+ $ this ->needReconnect = true ;
205
+ return true ;
168
206
}
169
207
170
208
public function stream_tell () {
171
209
return $ this ->offset ;
172
210
}
173
211
174
212
public function stream_stat () {
175
- if (is_resource ( $ this ->current )) {
176
- return fstat ($ this ->current );
213
+ if ($ this ->getCurrent ( )) {
214
+ return fstat ($ this ->getCurrent () );
177
215
} else {
178
216
return false ;
179
217
}
180
218
}
181
219
182
220
public function stream_eof () {
183
- if (is_resource ( $ this ->current )) {
184
- return feof ($ this ->current );
221
+ if ($ this ->getCurrent ( )) {
222
+ return feof ($ this ->getCurrent () );
185
223
} else {
186
224
return true ;
187
225
}
188
226
}
189
227
190
228
public function stream_close () {
191
- if (is_resource ( $ this ->current )) {
229
+ if ($ this ->hasOpenStream ( )) {
192
230
fclose ($ this ->current );
193
231
}
232
+ $ this ->current = null ;
194
233
}
195
234
196
235
public function stream_write ($ data ) {
0 commit comments