Skip to content

Commit 966511c

Browse files
committed
Merge pull request #491
2 parents 0d3496d + 010751c commit 966511c

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

src/GridFS/ReadableStream.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,32 @@ public function seek($offset)
186186
$this->chunkOffset = (integer) floor($offset / $this->chunkSize);
187187
$this->bufferOffset = $offset % $this->chunkSize;
188188

189-
if ($lastChunkOffset !== $this->chunkOffset) {
190-
$this->buffer = null;
189+
if ($lastChunkOffset === $this->chunkOffset) {
190+
return;
191+
}
192+
193+
if ($this->chunksIterator === null) {
194+
return;
195+
}
196+
197+
// Clear the buffer since the current chunk will be changed
198+
$this->buffer = null;
199+
200+
/* If we are seeking to a previous chunk, we need to reinitialize the
201+
* chunk iterator.
202+
*/
203+
if ($lastChunkOffset > $this->chunkOffset) {
191204
$this->chunksIterator = null;
205+
return;
206+
}
207+
208+
/* If we are seeking to a subsequent chunk, we do not need to
209+
* reinitalize the chunk iterator. Instead, we can simply move forward
210+
* to $this->chunkOffset.
211+
*/
212+
$numChunks = $this->chunkOffset - $lastChunkOffset;
213+
for ($i = 0; $i < $numChunks; $i++) {
214+
$this->chunksIterator->next();
192215
}
193216
}
194217

tests/GridFS/ReadableStreamFunctionalTest.php

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use MongoDB\BSON\Binary;
66
use MongoDB\GridFS\CollectionWrapper;
77
use MongoDB\GridFS\ReadableStream;
8+
use MongoDB\Tests\CommandObserver;
9+
use stdClass;
810

911
/**
1012
* Functional tests for the internal ReadableStream class.
@@ -188,6 +190,15 @@ public function testReadBytesWithNegativeLength()
188190
$stream->readBytes(-1);
189191
}
190192

193+
public function testSeekBeforeReading()
194+
{
195+
$fileDocument = $this->collectionWrapper->findFileById('length-10');
196+
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
197+
198+
$stream->seek(8);
199+
$this->assertSame('ij', $stream->readBytes(2));
200+
}
201+
191202
/**
192203
* @expectedException MongoDB\Exception\InvalidArgumentException
193204
* @expectedExceptionMessage $offset must be >= 0 and <= 10; given: 11
@@ -199,4 +210,109 @@ public function testSeekOutOfRange()
199210

200211
$stream->seek(11);
201212
}
213+
214+
/**
215+
* @dataProvider providePreviousChunkSeekOffsetAndBytes
216+
*/
217+
public function testSeekPreviousChunk($offset, $length, $expectedBytes)
218+
{
219+
$fileDocument = $this->collectionWrapper->findFileById('length-10');
220+
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
221+
222+
// Read to initialize and advance the chunk iterator to the last chunk
223+
$this->assertSame('abcdefghij', $stream->readBytes(10));
224+
225+
$commands = [];
226+
227+
(new CommandObserver)->observe(
228+
function() use ($stream, $offset, $length, $expectedBytes) {
229+
$stream->seek($offset);
230+
$this->assertSame($expectedBytes, $stream->readBytes($length));
231+
},
232+
function(stdClass $command) use (&$commands) {
233+
$commands[] = key((array) $command);
234+
}
235+
);
236+
237+
$this->assertSame(['find'], $commands);
238+
}
239+
240+
public function providePreviousChunkSeekOffsetAndBytes()
241+
{
242+
return [
243+
[0, 4, 'abcd'],
244+
[2, 4, 'cdef'],
245+
[4, 4, 'efgh'],
246+
[6, 4, 'ghij'],
247+
];
248+
}
249+
250+
/**
251+
* @dataProvider provideSameChunkSeekOffsetAndBytes
252+
*/
253+
public function testSeekSameChunk($offset, $length, $expectedBytes)
254+
{
255+
$fileDocument = $this->collectionWrapper->findFileById('length-10');
256+
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
257+
258+
// Read to initialize and advance the chunk iterator to the middle chunk
259+
$this->assertSame('abcdef', $stream->readBytes(6));
260+
261+
$commands = [];
262+
263+
(new CommandObserver)->observe(
264+
function() use ($stream, $offset, $length, $expectedBytes) {
265+
$stream->seek($offset);
266+
$this->assertSame($expectedBytes, $stream->readBytes($length));
267+
},
268+
function(stdClass $command) use (&$commands) {
269+
$commands[] = key((array) $command);
270+
}
271+
);
272+
273+
$this->assertSame([], $commands);
274+
}
275+
276+
public function provideSameChunkSeekOffsetAndBytes()
277+
{
278+
return [
279+
[4, 4, 'efgh'],
280+
[6, 4, 'ghij'],
281+
];
282+
}
283+
284+
/**
285+
* @dataProvider provideSubsequentChunkSeekOffsetAndBytes
286+
*/
287+
public function testSeekSubsequentChunk($offset, $length, $expectedBytes)
288+
{
289+
$fileDocument = $this->collectionWrapper->findFileById('length-10');
290+
$stream = new ReadableStream($this->collectionWrapper, $fileDocument);
291+
292+
// Read to initialize the chunk iterator to the first chunk
293+
$this->assertSame('a', $stream->readBytes(1));
294+
295+
$commands = [];
296+
297+
(new CommandObserver)->observe(
298+
function() use ($stream, $offset, $length, $expectedBytes) {
299+
$stream->seek($offset);
300+
$this->assertSame($expectedBytes, $stream->readBytes($length));
301+
},
302+
function(stdClass $command) use (&$commands) {
303+
$commands[] = key((array) $command);
304+
}
305+
);
306+
307+
$this->assertSame([], $commands);
308+
}
309+
310+
public function provideSubsequentChunkSeekOffsetAndBytes()
311+
{
312+
return [
313+
[4, 4, 'efgh'],
314+
[6, 4, 'ghij'],
315+
[8, 2, 'ij'],
316+
];
317+
}
202318
}

0 commit comments

Comments
 (0)