Commit 46864db5 authored by Jeremy Mikola's avatar Jeremy Mikola

Refactor GridFS internals

The Bucket's public API remains unchanged. More logic has been moved into the CollectionWrapper and the stream classes have also been renamed.

The StreamWrapper's registration has been changed to allow a configurable protocol (Bucket defaults to "gridfs"). While file paths are generated for readable and writable streams (now in a more robust manner), necessary arguments are passed through context options. We may decide to simplify the path for readable streams down the line, unless the current path is beneficial for debugging.

Closes #167
parent 2b93dab1
This diff is collapsed.
...@@ -3,8 +3,12 @@ ...@@ -3,8 +3,12 @@
namespace MongoDB\GridFS; namespace MongoDB\GridFS;
use MongoDB\Collection; use MongoDB\Collection;
use MongoDB\UpdateResult;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager; use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference; use MongoDB\Driver\ReadPreference;
use IteratorIterator;
use stdClass;
/** /**
* CollectionWrapper abstracts the GridFS files and chunks collections. * CollectionWrapper abstracts the GridFS files and chunks collections.
...@@ -14,7 +18,7 @@ use MongoDB\Driver\ReadPreference; ...@@ -14,7 +18,7 @@ use MongoDB\Driver\ReadPreference;
class CollectionWrapper class CollectionWrapper
{ {
private $chunksCollection; private $chunksCollection;
private $ensuredIndexes = false; private $checkedIndexes = false;
private $filesCollection; private $filesCollection;
/** /**
...@@ -33,34 +37,183 @@ class CollectionWrapper ...@@ -33,34 +37,183 @@ class CollectionWrapper
$this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions); $this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions);
} }
/**
* Deletes all GridFS chunks for a given file ID.
*
* @param mixed $id
*/
public function deleteChunksByFilesId($id)
{
$this->chunksCollection->deleteMany(['files_id' => $id]);
}
/**
* Deletes a GridFS file and related chunks by ID.
*
* @param mixed $id
*/
public function deleteFileAndChunksById($id)
{
$this->filesCollection->deleteOne(['_id' => $id]);
$this->chunksCollection->deleteMany(['files_id' => $id]);
}
/**
* Drops the GridFS files and chunks collections.
*/
public function dropCollections() public function dropCollections()
{ {
$this->filesCollection->drop(); $this->filesCollection->drop();
$this->chunksCollection->drop(); $this->chunksCollection->drop();
} }
/**
* Finds a GridFS file document for a given filename and revision.
*
* Revision numbers are defined as follows:
*
* * 0 = the original stored file
* * 1 = the first revision
* * 2 = the second revision
* * etc…
* * -2 = the second most recent revision
* * -1 = the most recent revision
*
* @see Bucket::downloadToStreamByName()
* @see Bucket::openDownloadStreamByName()
* @param string $filename
* @param integer $revision
* @return stdClass|null
*/
public function findFileByFilenameAndRevision($filename, $revision)
{
$filename = (string) $filename;
$revision = (integer) $revision;
if ($revision < 0) {
$skip = abs($revision) - 1;
$sortOrder = -1;
} else {
$skip = $revision;
$sortOrder = 1;
}
return $this->filesCollection->findOne(
['filename' => $filename],
[
'skip' => $skip,
'sort' => ['uploadDate' => $sortOrder],
'typeMap' => ['root' => 'stdClass'],
]
);
}
/**
* Finds a GridFS file document for a given ID.
*
* @param mixed $id
* @return stdClass|null
*/
public function findFileById($id)
{
return $this->filesCollection->findOne(
['_id' => $id],
['typeMap' => ['root' => 'stdClass']]
);
}
/**
* Finds documents from the GridFS bucket's files collection.
*
* @see Find::__construct() for supported options
* @param array|object $filter Query by which to filter documents
* @param array $options Additional options
* @return Cursor
*/
public function findFiles($filter, array $options = [])
{
return $this->filesCollection->find($filter, $options);
}
// TODO: Remove this
public function getChunksCollection() public function getChunksCollection()
{ {
return $this->chunksCollection; return $this->chunksCollection;
} }
/**
* Returns a chunks iterator for a given file ID.
*
* @param mixed $id
* @return IteratorIterator
*/
public function getChunksIteratorByFilesId($id)
{
$cursor = $this->chunksCollection->find(
['files_id' => $id],
[
'sort' => ['n' => 1],
'typeMap' => ['root' => 'stdClass'],
]
);
return new IteratorIterator($cursor);
}
// TODO: Remove this
public function getFilesCollection() public function getFilesCollection()
{ {
return $this->filesCollection; return $this->filesCollection;
} }
/**
* Inserts a document into the chunks collection.
*
* @param array|object $chunk Chunk document
*/
public function insertChunk($chunk) public function insertChunk($chunk)
{ {
if ( ! $this->checkedIndexes) {
$this->ensureIndexes(); $this->ensureIndexes();
}
$this->chunksCollection->insertOne($chunk); $this->chunksCollection->insertOne($chunk);
} }
/**
* Inserts a document into the files collection.
*
* The file document should be inserted after all chunks have been inserted.
*
* @param array|object $file File document
*/
public function insertFile($file) public function insertFile($file)
{ {
if ( ! $this->checkedIndexes) {
$this->ensureIndexes(); $this->ensureIndexes();
}
$this->filesCollection->insertOne($file); $this->filesCollection->insertOne($file);
} }
/**
* Updates the filename field in the file document for a given ID.
*
* @param mixed $id
* @param string $filename
* @return UpdateResult
*/
public function updateFilenameForId($id, $filename)
{
return $this->filesCollection->updateOne(
['_id' => $id],
['$set' => ['filename' => (string) $filename]]
);
}
/**
* Create an index on the chunks collection if it does not already exist.
*/
private function ensureChunksIndex() private function ensureChunksIndex()
{ {
foreach ($this->chunksCollection->listIndexes() as $index) { foreach ($this->chunksCollection->listIndexes() as $index) {
...@@ -72,6 +225,9 @@ class CollectionWrapper ...@@ -72,6 +225,9 @@ class CollectionWrapper
$this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]); $this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
} }
/**
* Create an index on the files collection if it does not already exist.
*/
private function ensureFilesIndex() private function ensureFilesIndex()
{ {
foreach ($this->filesCollection->listIndexes() as $index) { foreach ($this->filesCollection->listIndexes() as $index) {
...@@ -83,21 +239,33 @@ class CollectionWrapper ...@@ -83,21 +239,33 @@ class CollectionWrapper
$this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]); $this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
} }
/**
* Ensure indexes on the files and chunks collections exist.
*
* This method is called once before the first write operation on a GridFS
* bucket. Indexes are only be created if the files collection is empty.
*/
private function ensureIndexes() private function ensureIndexes()
{ {
if ($this->ensuredIndexes) { if ($this->checkedIndexes) {
return; return;
} }
$this->checkedIndexes = true;
if ( ! $this->isFilesCollectionEmpty()) { if ( ! $this->isFilesCollectionEmpty()) {
return; return;
} }
$this->ensureFilesIndex(); $this->ensureFilesIndex();
$this->ensureChunksIndex(); $this->ensureChunksIndex();
$this->ensuredIndexes = true;
} }
/**
* Returns whether the files collection is empty.
*
* @return boolean
*/
private function isFilesCollectionEmpty() private function isFilesCollectionEmpty()
{ {
return null === $this->filesCollection->findOne([], [ return null === $this->filesCollection->findOne([], [
......
...@@ -7,26 +7,25 @@ use MongoDB\GridFS\Exception\CorruptFileException; ...@@ -7,26 +7,25 @@ use MongoDB\GridFS\Exception\CorruptFileException;
use stdClass; use stdClass;
/** /**
* GridFSDownload abstracts the process of reading a GridFS file. * ReadableStream abstracts the process of reading a GridFS file.
* *
* @internal * @internal
*/ */
class GridFSDownload class ReadableStream
{ {
private $buffer; private $buffer;
private $bufferEmpty = true; private $bufferEmpty;
private $bufferFresh = true; private $bufferFresh;
private $bytesSeen = 0; private $bytesSeen = 0;
private $chunkOffset = 0; private $chunkOffset = 0;
private $chunksIterator; private $chunksIterator;
private $collectionWrapper;
private $file; private $file;
private $firstCheck = true; private $firstCheck = true;
private $iteratorEmpty = false; private $iteratorEmpty = false;
private $numChunks; private $numChunks;
/** /**
* Constructs a GridFS download stream. * Constructs a readable GridFS stream.
* *
* @param CollectionWrapper $collectionWrapper GridFS collection wrapper * @param CollectionWrapper $collectionWrapper GridFS collection wrapper
* @param stdClass $file GridFS file document * @param stdClass $file GridFS file document
...@@ -34,22 +33,11 @@ class GridFSDownload ...@@ -34,22 +33,11 @@ class GridFSDownload
*/ */
public function __construct(CollectionWrapper $collectionWrapper, stdClass $file) public function __construct(CollectionWrapper $collectionWrapper, stdClass $file)
{ {
$this->collectionWrapper = $collectionWrapper;
$this->file = $file; $this->file = $file;
try { $this->chunksIterator = $collectionWrapper->getChunksIteratorByFilesId($this->file->_id);
$cursor = $this->collectionWrapper->getChunksCollection()->find(
['files_id' => $this->file->_id],
['sort' => ['n' => 1]]
);
} catch (Exception $e) {
// TODO: Why do we replace a driver exception with CorruptFileException here?
throw new CorruptFileException();
}
$this->chunksIterator = new \IteratorIterator($cursor);
$this->numChunks = ($file->length >= 0) ? ceil($file->length / $file->chunkSize) : 0; $this->numChunks = ($file->length >= 0) ? ceil($file->length / $file->chunkSize) : 0;
$this->buffer = fopen('php://temp', 'w+'); $this->initEmptyBuffer();
} }
public function close() public function close()
...@@ -57,32 +45,35 @@ class GridFSDownload ...@@ -57,32 +45,35 @@ class GridFSDownload
fclose($this->buffer); fclose($this->buffer);
} }
public function downloadNumBytes($numToRead) /**
* Read bytes from the stream.
*
* Note: this method may return a string smaller than the requested length
* if data is not available to be read.
*
* @param integer $numBytes Number of bytes to read
* @return string
*/
public function downloadNumBytes($numBytes)
{ {
$output = "";
if ($this->bufferFresh) { if ($this->bufferFresh) {
rewind($this->buffer); rewind($this->buffer);
$this->bufferFresh = false; $this->bufferFresh = false;
} }
// TODO: Should we be checking for fread errors here? // TODO: Should we be checking for fread errors here?
$output = fread($this->buffer, $numToRead); $output = fread($this->buffer, $numBytes);
if (strlen($output) == $numToRead) { if (strlen($output) == $numBytes) {
return $output; return $output;
} }
fclose($this->buffer); $this->initEmptyBuffer();
$this->buffer = fopen("php://temp", "w+");
$this->bufferFresh = true;
$this->bufferEmpty = true;
$bytesLeft = $numToRead - strlen($output); $bytesLeft = $numBytes - strlen($output);
while (strlen($output) < $numToRead && $this->advanceChunks()) { while (strlen($output) < $numBytes && $this->advanceChunks()) {
$bytesLeft = $numToRead - strlen($output); $bytesLeft = $numBytes - strlen($output);
$output .= substr($this->chunksIterator->current()->data->getData(), 0, $bytesLeft); $output .= substr($this->chunksIterator->current()->data->getData(), 0, $bytesLeft);
} }
...@@ -94,8 +85,18 @@ class GridFSDownload ...@@ -94,8 +85,18 @@ class GridFSDownload
return $output; return $output;
} }
/**
* Writes the contents of this GridFS file to a writable stream.
*
* @param resource $destination Writable stream
* @throws InvalidArgumentException
*/
public function downloadToStream($destination) public function downloadToStream($destination)
{ {
if ( ! is_resource($destination) || get_resource_type($destination) != "stream") {
throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
}
while ($this->advanceChunks()) { while ($this->advanceChunks()) {
// TODO: Should we be checking for fwrite errors here? // TODO: Should we be checking for fwrite errors here?
fwrite($destination, $this->chunksIterator->current()->data->getData()); fwrite($destination, $this->chunksIterator->current()->data->getData());
...@@ -160,4 +161,15 @@ class GridFSDownload ...@@ -160,4 +161,15 @@ class GridFSDownload
return true; return true;
} }
private function initEmptyBuffer()
{
if (isset($this->buffer)) {
fclose($this->buffer);
}
$this->buffer = fopen("php://temp", "w+");
$this->bufferEmpty = true;
$this->bufferFresh = true;
}
} }
...@@ -11,96 +11,130 @@ namespace MongoDB\GridFS; ...@@ -11,96 +11,130 @@ namespace MongoDB\GridFS;
*/ */
class StreamWrapper class StreamWrapper
{ {
/**
* @var resource|null Stream context (set by PHP)
*/
public $context; public $context;
private $collectionsWrapper;
private $gridFSStream;
private $id;
private $mode; private $mode;
private $protocol;
private $stream;
public function getId() public function getId()
{ {
return $this->id; return $this->stream->getId();
}
public function openReadStream()
{
$context = stream_context_get_options($this->context);
$this->gridFSStream = new GridFSDownload($this->collectionWrapper, $context['gridfs']['file']);
$this->id = $this->gridFSStream->getId();
return true;
}
public function openWriteStream()
{
$context = stream_context_get_options($this->context);
$options = $context['gridfs']['uploadOptions'];
$this->gridFSStream = new GridFSUpload($this->collectionWrapper, $this->identifier, $options);
$this->id = $this->gridFSStream->getId();
return true;
} }
/** /**
* Register the GridFS stream wrapper. * Register the GridFS stream wrapper.
*
* @param string $protocol Protocol to use for stream_wrapper_register()
*/ */
public static function register() public static function register($protocol = 'gridfs')
{ {
if (in_array('gridfs', stream_get_wrappers())) { if (in_array($protocol, stream_get_wrappers())) {
stream_wrapper_unregister('gridfs'); stream_wrapper_unregister($protocol);
} }
stream_wrapper_register('gridfs', get_called_class(), \STREAM_IS_URL); stream_wrapper_register($protocol, get_called_class(), \STREAM_IS_URL);
} }
/**
* Closes the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-close.php
*/
public function stream_close() public function stream_close()
{ {
$this->gridFSStream->close(); $this->stream->close();
} }
/**
* Returns whether the file pointer is at the end of the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-eof.php
* @return boolean
*/
public function stream_eof() public function stream_eof()
{ {
return $this->gridFSStream->isEOF(); return $this->stream->isEOF();
} }
/**
* Opens the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-open.php
* @param string $path Path to the file resource
* @param string $mode Mode used to open the file (only "r" and "w" are supported)
* @param integer $options Additional flags set by the streams API
* @param string $openedPath Not used
*/
public function stream_open($path, $mode, $options, &$openedPath) public function stream_open($path, $mode, $options, &$openedPath)
{ {
$this->initProtocol($path); $this->initProtocol($path);
$context = stream_context_get_options($this->context);
$this->collectionWrapper = $context['gridfs']['collectionWrapper'];
$this->mode = $mode; $this->mode = $mode;
switch ($this->mode) { if ($mode === 'r') {
case 'r': return $this->openReadStream(); return $this->initReadableStream();
case 'w': return $this->openWriteStream(); }
default: return false;
if ($mode === 'w') {
return $this->initWritableStream();
} }
return false;
} }
/**
* Read bytes from the stream.
*
* Note: this method may return a string smaller than the requested length
* if data is not available to be read.
*
* @see http://php.net/manual/en/streamwrapper.stream-read.php
* @param integer $count Number of bytes to read
* @return string
*/
public function stream_read($count) public function stream_read($count)
{ {
return $this->gridFSStream->downloadNumBytes($count); // TODO: Ensure that $this->stream is a ReadableStream
return $this->stream->downloadNumBytes($count);
} }
/**
* Return information about the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-stat.php
* @return array
*/
public function stream_stat() public function stream_stat()
{ {
$stat = $this->getStatTemplate(); $stat = $this->getStatTemplate();
$stat[7] = $stat['size'] = $this->gridFSStream->getSize();
$stat[2] = $stat['mode'] = $this->mode;
$stat[7] = $stat['size'] = $this->stream->getSize();
return $stat; return $stat;
} }
/**
* Write bytes to the stream.
*
* @see http://php.net/manual/en/streamwrapper.stream-write.php
* @param string $data Data to write
* @return integer The number of bytes successfully stored
*/
public function stream_write($data) public function stream_write($data)
{ {
$this->gridFSStream->insertChunks($data); // TODO: Ensure that $this->stream is a WritableStream
$this->stream->insertChunks($data);
return strlen($data); return strlen($data);
} }
/** /**
* Gets a URL stat template with default values * Returns a stat template with default values.
* from https://github.com/aws/aws-sdk-php/blob/master/src/S3/StreamWrapper.php *
* @return array * @return array
*/ */
private function getStatTemplate() private function getStatTemplate()
...@@ -122,9 +156,52 @@ class StreamWrapper ...@@ -122,9 +156,52 @@ class StreamWrapper
]; ];
} }
/**
* Initialize the protocol from the given path.
*
* @see StreamWrapper::stream_open()
* @param string $path
*/
private function initProtocol($path) private function initProtocol($path)
{ {
$parsed_path = parse_url($path); $parts = explode('://', $path, 2);
$this->identifier = substr($parsed_path['path'], 1); $this->protocol = $parts[0] ?: 'gridfs';
}
/**
* Initialize the internal stream for reading.
*
* @see StreamWrapper::stream_open()
* @return boolean
*/
private function initReadableStream()
{
$context = stream_context_get_options($this->context);
$this->stream = new ReadableStream(
$context[$this->protocol]['collectionWrapper'],
$context[$this->protocol]['file']
);
return true;
}
/**
* Initialize the internal stream for writing.
*
* @see StreamWrapper::stream_open()
* @return boolean
*/
private function initWritableStream()
{
$context = stream_context_get_options($this->context);
$this->stream = new WritableStream(
$context[$this->protocol]['collectionWrapper'],
$context[$this->protocol]['filename'],
$context[$this->protocol]['options']
);
return true;
} }
} }
...@@ -5,16 +5,18 @@ namespace MongoDB\GridFS; ...@@ -5,16 +5,18 @@ namespace MongoDB\GridFS;
use MongoDB\BSON\Binary; use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectId; use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime; use MongoDB\BSON\UTCDateTime;
use MongoDB\Driver\Exception\Exception; use MongoDB\Driver\Exception\Exception as DriverException;
use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\InvalidArgumentException;
/** /**
* GridFSUpload abstracts the process of writing a GridFS file. * WritableStream abstracts the process of writing a GridFS file.
* *
* @internal * @internal
*/ */
class GridFSUpload class WritableStream
{ {
private static $defaultChunkSizeBytes = 261120;
private $buffer; private $buffer;
private $bufferLength = 0; private $bufferLength = 0;
private $chunkOffset = 0; private $chunkOffset = 0;
...@@ -22,15 +24,16 @@ class GridFSUpload ...@@ -22,15 +24,16 @@ class GridFSUpload
private $collectionWrapper; private $collectionWrapper;
private $ctx; private $ctx;
private $file; private $file;
private $indexChecker;
private $isClosed = false; private $isClosed = false;
private $length = 0; private $length = 0;
/** /**
* Constructs a GridFS upload stream. * Constructs a writable GridFS stream.
* *
* Supported options: * Supported options:
* *
* * _id (mixed): File document identifier. Defaults to a new ObjectId.
*
* * aliases (array of strings): DEPRECATED An array of aliases. * * aliases (array of strings): DEPRECATED An array of aliases.
* Applications wishing to store aliases should add an aliases field to * Applications wishing to store aliases should add an aliases field to
* the metadata document instead. * the metadata document instead.
...@@ -51,12 +54,19 @@ class GridFSUpload ...@@ -51,12 +54,19 @@ class GridFSUpload
*/ */
public function __construct(CollectionWrapper $collectionWrapper, $filename, array $options = []) public function __construct(CollectionWrapper $collectionWrapper, $filename, array $options = [])
{ {
$options += ['chunkSizeBytes' => 261120]; $options += [
'_id' => new ObjectId,
'chunkSizeBytes' => self::$defaultChunkSizeBytes,
];
if (isset($options['aliases']) && ! \MongoDB\is_string_array($options['aliases'])) { if (isset($options['aliases']) && ! \MongoDB\is_string_array($options['aliases'])) {
throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings'); throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings');
} }
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
}
if (isset($options['contentType']) && ! is_string($options['contentType'])) { if (isset($options['contentType']) && ! is_string($options['contentType'])) {
throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string'); throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string');
} }
...@@ -71,10 +81,11 @@ class GridFSUpload ...@@ -71,10 +81,11 @@ class GridFSUpload
$this->ctx = hash_init('md5'); $this->ctx = hash_init('md5');
$this->file = [ $this->file = [
'_id' => new ObjectId(), '_id' => $options['_id'],
'chunkSize' => $this->chunkSize, 'chunkSize' => $this->chunkSize,
'filename' => (string) $filename, 'filename' => (string) $filename,
'uploadDate' => $this->createUploadDate(), // TODO: This is necessary until PHPC-536 is implemented
'uploadDate' => new UTCDateTime(floor(microtime(true) * 1000)),
] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]); ] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]);
} }
...@@ -133,7 +144,7 @@ class GridFSUpload ...@@ -133,7 +144,7 @@ class GridFSUpload
* reset. * reset.
* *
* @param string $toWrite Binary data to write * @param string $toWrite Binary data to write
* @return int * @return integer
*/ */
public function insertChunks($toWrite) public function insertChunks($toWrite)
{ {
...@@ -171,6 +182,7 @@ class GridFSUpload ...@@ -171,6 +182,7 @@ class GridFSUpload
* *
* @param resource $source Readable stream * @param resource $source Readable stream
* @return ObjectId * @return ObjectId
* @throws InvalidArgumentException
*/ */
public function uploadFromStream($source) public function uploadFromStream($source)
{ {
...@@ -178,8 +190,6 @@ class GridFSUpload ...@@ -178,8 +190,6 @@ class GridFSUpload
throw InvalidArgumentException::invalidType('$source', $source, 'resource'); throw InvalidArgumentException::invalidType('$source', $source, 'resource');
} }
$streamMetadata = stream_get_meta_data($source);
while ($data = $this->readChunk($source)) { while ($data = $this->readChunk($source)) {
$this->insertChunk($data); $this->insertChunk($data);
} }
...@@ -189,20 +199,10 @@ class GridFSUpload ...@@ -189,20 +199,10 @@ class GridFSUpload
private function abort() private function abort()
{ {
$this->collectionWrapper->getChunksCollection()->deleteMany(['files_id' => $this->file['_id']]); $this->collectionWrapper->deleteChunksByFilesId($this->file['_id']);
$this->collectionWrapper->getFilesCollection()->deleteOne(['_id' => $this->file['_id']]);
$this->isClosed = true; $this->isClosed = true;
} }
// From: http://stackoverflow.com/questions/3656713/how-to-get-current-time-in-milliseconds-in-php
private function createUploadDate()
{
$parts = explode(' ', microtime());
$milliseconds = sprintf('%d%03d', $parts[1], $parts[0] * 1000);
return new UTCDateTime($milliseconds);
}
private function fileCollectionInsert() private function fileCollectionInsert()
{ {
if ($this->isClosed) { if ($this->isClosed) {
...@@ -244,7 +244,7 @@ class GridFSUpload ...@@ -244,7 +244,7 @@ class GridFSUpload
{ {
try { try {
$data = fread($source, $this->chunkSize); $data = fread($source, $this->chunkSize);
} catch (Exception $e) { } catch (DriverException $e) {
$this->abort(); $this->abort();
throw $e; throw $e;
} }
......
...@@ -63,8 +63,8 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -63,8 +63,8 @@ class BucketFunctionalTest extends FunctionalTestCase
$id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello world")); $id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello world"));
$contents = stream_get_contents($this->bucket->openDownloadStream($id)); $contents = stream_get_contents($this->bucket->openDownloadStream($id));
$this->assertEquals("hello world", $contents); $this->assertEquals("hello world", $contents);
$this->assertEquals(1, $this->bucket->getCollectionsWrapper()->getFilesCollection()->count()); $this->assertEquals(1, $this->bucket->getCollectionWrapper()->getFilesCollection()->count());
$this->assertEquals(1, $this->bucket->getCollectionsWrapper()->getChunksCollection()->count()); $this->assertEquals(1, $this->bucket->getCollectionWrapper()->getChunksCollection()->count());
$this->bucket->delete($id); $this->bucket->delete($id);
$error=null; $error=null;
...@@ -75,17 +75,17 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -75,17 +75,17 @@ class BucketFunctionalTest extends FunctionalTestCase
} }
$fileNotFound = '\MongoDB\GridFS\Exception\FileNotFoundException'; $fileNotFound = '\MongoDB\GridFS\Exception\FileNotFoundException';
$this->assertTrue($error instanceof $fileNotFound); $this->assertTrue($error instanceof $fileNotFound);
$this->assertEquals(0, $this->bucket->getCollectionsWrapper()->getFilesCollection()->count()); $this->assertEquals(0, $this->bucket->getCollectionWrapper()->getFilesCollection()->count());
$this->assertEquals(0, $this->bucket->getCollectionsWrapper()->getChunksCollection()->count()); $this->assertEquals(0, $this->bucket->getCollectionWrapper()->getChunksCollection()->count());
} }
public function testMultiChunkDelete() public function testMultiChunkDelete()
{ {
$id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello"), ['chunkSizeBytes'=>1]); $id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello"), ['chunkSizeBytes'=>1]);
$this->assertEquals(1, $this->bucket->getCollectionsWrapper()->getFilesCollection()->count()); $this->assertEquals(1, $this->bucket->getCollectionWrapper()->getFilesCollection()->count());
$this->assertEquals(5, $this->bucket->getCollectionsWrapper()->getChunksCollection()->count()); $this->assertEquals(5, $this->bucket->getCollectionWrapper()->getChunksCollection()->count());
$this->bucket->delete($id); $this->bucket->delete($id);
$this->assertEquals(0, $this->bucket->getCollectionsWrapper()->getFilesCollection()->count()); $this->assertEquals(0, $this->bucket->getCollectionWrapper()->getFilesCollection()->count());
$this->assertEquals(0, $this->bucket->getCollectionsWrapper()->getChunksCollection()->count()); $this->assertEquals(0, $this->bucket->getCollectionWrapper()->getChunksCollection()->count());
} }
public function testEmptyFile() public function testEmptyFile()
...@@ -93,10 +93,10 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -93,10 +93,10 @@ class BucketFunctionalTest extends FunctionalTestCase
$id = $this->bucket->uploadFromStream("test_filename",$this->generateStream("")); $id = $this->bucket->uploadFromStream("test_filename",$this->generateStream(""));
$contents = stream_get_contents($this->bucket->openDownloadStream($id)); $contents = stream_get_contents($this->bucket->openDownloadStream($id));
$this->assertEquals("", $contents); $this->assertEquals("", $contents);
$this->assertEquals(1, $this->bucket->getCollectionsWrapper()->getFilesCollection()->count()); $this->assertEquals(1, $this->bucket->getCollectionWrapper()->getFilesCollection()->count());
$this->assertEquals(0, $this->bucket->getCollectionsWrapper()->getChunksCollection()->count()); $this->assertEquals(0, $this->bucket->getCollectionWrapper()->getChunksCollection()->count());
$raw = $this->bucket->getCollectionsWrapper()->getFilesCollection()->findOne(); $raw = $this->bucket->getCollectionWrapper()->getFilesCollection()->findOne();
$this->assertEquals(0, $raw->length); $this->assertEquals(0, $raw->length);
$this->assertEquals($id, $raw->_id); $this->assertEquals($id, $raw->_id);
$this->assertTrue($raw->uploadDate instanceof \MongoDB\BSON\UTCDateTime); $this->assertTrue($raw->uploadDate instanceof \MongoDB\BSON\UTCDateTime);
...@@ -107,7 +107,7 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -107,7 +107,7 @@ class BucketFunctionalTest extends FunctionalTestCase
{ {
$id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("foobar")); $id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("foobar"));
$this->collectionsWrapper->getChunksCollection()->updateOne(['files_id' => $id], $this->collectionWrapper->getChunksCollection()->updateOne(['files_id' => $id],
['$set' => ['data' => new \MongoDB\BSON\Binary('foo', \MongoDB\BSON\Binary::TYPE_GENERIC)]]); ['$set' => ['data' => new \MongoDB\BSON\Binary('foo', \MongoDB\BSON\Binary::TYPE_GENERIC)]]);
$error = null; $error = null;
try{ try{
...@@ -123,7 +123,7 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -123,7 +123,7 @@ class BucketFunctionalTest extends FunctionalTestCase
{ {
$id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello world,abcdefghijklmnopqrstuv123456789"), ["chunkSizeBytes" => 1]); $id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello world,abcdefghijklmnopqrstuv123456789"), ["chunkSizeBytes" => 1]);
$this->collectionsWrapper->getChunksCollection()->deleteOne(['files_id' => $id, 'n' => 7]); $this->collectionWrapper->getChunksCollection()->deleteOne(['files_id' => $id, 'n' => 7]);
$error = null; $error = null;
try{ try{
$download = $this->bucket->openDownloadStream($id); $download = $this->bucket->openDownloadStream($id);
...@@ -136,8 +136,8 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -136,8 +136,8 @@ class BucketFunctionalTest extends FunctionalTestCase
} }
public function testUploadEnsureIndexes() public function testUploadEnsureIndexes()
{ {
$chunks = $this->bucket->getCollectionsWrapper()->getChunksCollection(); $chunks = $this->bucket->getCollectionWrapper()->getChunksCollection();
$files = $this->bucket->getCollectionsWrapper()->getFilesCollection(); $files = $this->bucket->getCollectionWrapper()->getFilesCollection();
$this->bucket->uploadFromStream("filename", $this->generateStream("junk")); $this->bucket->uploadFromStream("filename", $this->generateStream("junk"));
$chunksIndexed = false; $chunksIndexed = false;
...@@ -238,7 +238,7 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -238,7 +238,7 @@ class BucketFunctionalTest extends FunctionalTestCase
public function testGridInNonIntChunksize() public function testGridInNonIntChunksize()
{ {
$id = $this->bucket->uploadFromStream("f",$this->generateStream("data")); $id = $this->bucket->uploadFromStream("f",$this->generateStream("data"));
$this->bucket->getCollectionsWrapper()->getFilesCollection()->updateOne(["filename"=>"f"], $this->bucket->getCollectionWrapper()->getFilesCollection()->updateOne(["filename"=>"f"],
['$set'=> ['chunkSize' => 100.00]]); ['$set'=> ['chunkSize' => 100.00]]);
$this->assertEquals("data", stream_get_contents($this->bucket->openDownloadStream($id))); $this->assertEquals("data", stream_get_contents($this->bucket->openDownloadStream($id)));
} }
...@@ -288,7 +288,7 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -288,7 +288,7 @@ class BucketFunctionalTest extends FunctionalTestCase
$id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello world")); $id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello world"));
$this->bucket->drop(); $this->bucket->drop();
$id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello world")); $id = $this->bucket->uploadFromStream("test_filename", $this->generateStream("hello world"));
$this->assertEquals(1, $this->collectionsWrapper->getFilesCollection()->count()); $this->assertEquals(1, $this->collectionWrapper->getFilesCollection()->count());
} }
/** /**
*@dataProvider provideInsertChunks *@dataProvider provideInsertChunks
......
...@@ -12,7 +12,7 @@ use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase; ...@@ -12,7 +12,7 @@ use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase;
abstract class FunctionalTestCase extends BaseFunctionalTestCase abstract class FunctionalTestCase extends BaseFunctionalTestCase
{ {
protected $bucket; protected $bucket;
protected $collectionsWrapper; protected $collectionWrapper;
public function setUp() public function setUp()
{ {
...@@ -22,7 +22,7 @@ abstract class FunctionalTestCase extends BaseFunctionalTestCase ...@@ -22,7 +22,7 @@ abstract class FunctionalTestCase extends BaseFunctionalTestCase
$col->drop(); $col->drop();
} }
$this->bucket = new \MongoDB\GridFS\Bucket($this->manager, $this->getDatabaseName()); $this->bucket = new \MongoDB\GridFS\Bucket($this->manager, $this->getDatabaseName());
$this->collectionsWrapper = $this->bucket->getCollectionsWrapper(); $this->collectionWrapper = $this->bucket->getCollectionWrapper();
} }
public function tearDown() public function tearDown()
......
This diff is collapsed.
...@@ -78,9 +78,9 @@ class SpecificationTests extends FunctionalTestCase ...@@ -78,9 +78,9 @@ class SpecificationTests extends FunctionalTestCase
$fixedAssertTrue = $this->fixTypes($test['assert'], true); $fixedAssertTrue = $this->fixTypes($test['assert'], true);
if (isset($test['assert']['data'])) { if (isset($test['assert']['data'])) {
$this->runCommands($fixedAssertTrue['data'], $result); $this->runCommands($fixedAssertTrue['data'], $result);
$this->collectionsEqual($this->collections['expected.files'],$this->bucket->getCollectionsWrapper()->getFilesCollection()); $this->collectionsEqual($this->collections['expected.files'],$this->bucket->getCollectionWrapper()->getFilesCollection());
if(isset($this->collections['expected.chunks'])) { if(isset($this->collections['expected.chunks'])) {
$this->collectionsEqual($this->collections['expected.chunks'],$this->bucket->getCollectionsWrapper()->getChunksCollection()); $this->collectionsEqual($this->collections['expected.chunks'],$this->bucket->getCollectionWrapper()->getChunksCollection());
} }
} }
} }
......
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