Commit 480da55f authored by Jeremy Mikola's avatar Jeremy Mikola

PHPLIB-213: Support seeking and partial file retrieval for GridFS streams

parent 90967fca
...@@ -166,6 +166,44 @@ class ReadableStream ...@@ -166,6 +166,44 @@ class ReadableStream
return $data; return $data;
} }
/**
* Seeks the chunk and buffer offsets for the next read operation.
*
* @param integer $offset
* @throws InvalidArgumentException if $offset is out of range
*/
public function seek($offset)
{
if ($offset < 0 || $offset > $this->file->length) {
throw new InvalidArgumentException(sprintf('$offset must be >= 0 and <= %d; given: %d', $length, $offset));
}
/* Compute the offsets for the chunk and buffer (i.e. chunk data) from
* which we will expect to read after seeking. If the chunk offset
* changed, we'll also need to reset the buffer.
*/
$lastChunkOffset = $this->chunkOffset;
$this->chunkOffset = (integer) floor($offset / $this->chunkSize);
$this->bufferOffset = $offset % $this->chunkSize;
if ($lastChunkOffset !== $this->chunkOffset) {
$this->buffer = null;
$this->chunksIterator = null;
}
}
/**
* Return the current position of the stream.
*
* This is the offset within the stream where the next byte would be read.
*
* @return integer
*/
public function tell()
{
return ($this->chunkOffset * $this->chunkSize) + $this->bufferOffset;
}
/** /**
* Initialize the buffer to the current chunk's data. * Initialize the buffer to the current chunk's data.
* *
......
...@@ -136,6 +136,40 @@ class StreamWrapper ...@@ -136,6 +136,40 @@ class StreamWrapper
} }
} }
/**
* Return the current position of the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-seek.php
* @param integer $offset Stream offset to seek to
* @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
* @return boolean True if the position was updated and false otherwise
*/
public function stream_seek($offset, $whence = \SEEK_SET)
{
$size = $this->stream->getSize();
if ($whence === \SEEK_CUR) {
$offset += $this->stream->tell();
}
if ($whence === \SEEK_END) {
$offset += $size;
}
// WritableStreams are always positioned at the end of the stream
if ($this->stream instanceof WritableStream) {
return $offset === $size;
}
if ($offset < 0 || $offset > $size) {
return false;
}
$this->stream->seek($offset);
return true;
}
/** /**
* Return information about the stream. * Return information about the stream.
* *
...@@ -166,6 +200,17 @@ class StreamWrapper ...@@ -166,6 +200,17 @@ class StreamWrapper
return $stat; return $stat;
} }
/**
* Return the current position of the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-tell.php
* @return integer The current position of the stream
*/
public function stream_tell()
{
return $this->stream->tell();
}
/** /**
* Write bytes to the stream. * Write bytes to the stream.
* *
......
...@@ -161,6 +161,21 @@ class WritableStream ...@@ -161,6 +161,21 @@ class WritableStream
return $this->length + strlen($this->buffer); return $this->length + strlen($this->buffer);
} }
/**
* Return the current position of the stream.
*
* This is the offset within the stream where the next byte would be
* written. Since seeking is not supported and writes are appended, this is
* always the end of the stream.
*
* @see WriteableStream::getSize()
* @return integer
*/
public function tell()
{
return $this->getSize();
}
/** /**
* Inserts binary data into GridFS via chunks. * Inserts binary data into GridFS via chunks.
* *
......
...@@ -50,6 +50,32 @@ class StreamWrapperFunctionalTest extends FunctionalTestCase ...@@ -50,6 +50,32 @@ class StreamWrapperFunctionalTest extends FunctionalTestCase
$this->assertSame('', fread($stream, 3)); $this->assertSame('', fread($stream, 3));
} }
public function testReadableStreamSeek()
{
$stream = $this->bucket->openDownloadStream('length-10');
$this->assertSame(0, fseek($stream, 2, \SEEK_SET));
$this->assertSame('cde', fread($stream, 3));
$this->assertSame(0, fseek($stream, 10, \SEEK_SET));
$this->assertSame('', fread($stream, 3));
$this->assertSame(-1, fseek($stream, -1, \SEEK_SET));
$this->assertSame(-1, fseek($stream, 11, \SEEK_SET));
$this->assertSame(0, fseek($stream, -5, \SEEK_CUR));
$this->assertSame('fgh', fread($stream, 3));
$this->assertSame(0, fseek($stream, 1, \SEEK_CUR));
$this->assertSame('j', fread($stream, 3));
$this->assertSame(-1, fseek($stream, 1, \SEEK_CUR));
$this->assertSame(-1, fseek($stream, -11, \SEEK_CUR));
$this->assertSame(0, fseek($stream, 0, \SEEK_END));
$this->assertSame('', fread($stream, 3));
$this->assertSame(0, fseek($stream, -8, \SEEK_END));
$this->assertSame('cde', fread($stream, 3));
$this->assertSame(-1, fseek($stream, -11, \SEEK_END));
$this->assertSame(-1, fseek($stream, 1, \SEEK_END));
}
public function testReadableStreamStat() public function testReadableStreamStat()
{ {
$stream = $this->bucket->openDownloadStream('length-10'); $stream = $this->bucket->openDownloadStream('length-10');
...@@ -102,6 +128,25 @@ class StreamWrapperFunctionalTest extends FunctionalTestCase ...@@ -102,6 +128,25 @@ class StreamWrapperFunctionalTest extends FunctionalTestCase
$this->assertSame('', fread($stream, 8192)); $this->assertSame('', fread($stream, 8192));
} }
public function testWritableStreamSeek()
{
$stream = $this->bucket->openUploadStream('filename');
$this->assertSame(6, fwrite($stream, 'foobar'));
$this->assertSame(-1, fseek($stream, 0, \SEEK_SET));
$this->assertSame(-1, fseek($stream, 7, \SEEK_SET));
$this->assertSame(0, fseek($stream, 6, \SEEK_SET));
$this->assertSame(0, fseek($stream, 0, \SEEK_CUR));
$this->assertSame(-1, fseek($stream, -1, \SEEK_CUR));
$this->assertSame(-1, fseek($stream, 1, \SEEK_CUR));
$this->assertSame(0, fseek($stream, 0, \SEEK_END));
$this->assertSame(-1, fseek($stream, -1, \SEEK_END));
$this->assertSame(-1, fseek($stream, 1, \SEEK_END));
}
public function testWritableStreamStat() public function testWritableStreamStat()
{ {
$currentTimestamp = time(); $currentTimestamp = time();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment