Commit f2666e19 authored by Jeremy Mikola's avatar Jeremy Mikola

Apply CS fixes and refactor GridFS classes

parent 0b31800c
...@@ -4,7 +4,8 @@ namespace MongoDB\Exception; ...@@ -4,7 +4,8 @@ namespace MongoDB\Exception;
class GridFSFileNotFoundException extends \MongoDB\Driver\Exception\RuntimeException implements Exception class GridFSFileNotFoundException extends \MongoDB\Driver\Exception\RuntimeException implements Exception
{ {
public function __construct($fname, $nameSpace){ public function __construct($filename, $namespace)
parent::__construct(sprintf('Unable to find file by: %s in %s', $fname,$nameSpace)); {
} parent::__construct(sprintf('Unable to find file "%s" in namespace "%s"', $filename, $namespace));
}
} }
<?php <?php
namespace MongoDB\GridFS; namespace MongoDB\GridFS;
use MongoDB\Collection;
use MongoDB\BSON\ObjectId; use MongoDB\BSON\ObjectId;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager; use MongoDB\Driver\Manager;
use MongoDB\Exception\GridFSFileNotFoundException;
use MongoDB\Exception\InvalidArgumentTypeException;
use MongoDB\Operation\Find;
/** /**
* Bucket provides a public API for interacting with the GridFS files and chunks
* collections.
*
* @api * @api
* Bucket abstracts the GridFS files and chunks collections.
*/ */
class Bucket class Bucket
{ {
private $databaseName; private static $streamWrapper;
private $collectionsWrapper; private $collectionsWrapper;
private $databaseName;
private $options; private $options;
private static $streamWrapper;
/** /**
* Constructs a GridFS bucket. * Constructs a GridFS bucket.
* *
...@@ -29,199 +38,324 @@ class Bucket ...@@ -29,199 +38,324 @@ class Bucket
* *
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
* *
* @param Manager $manager Manager instance from the driver * @param Manager $manager Manager instance from the driver
* @param string $databaseName Database name * @param string $databaseName Database name
* @param array $options Bucket options * @param array $options Bucket options
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function __construct(Manager $manager, $databaseName, array $options = []) public function __construct(Manager $manager, $databaseName, array $options = [])
{ {
$options += [ $options += [
'bucketName' => 'fs',
'chunkSizeBytes' => 261120, 'chunkSizeBytes' => 261120,
'bucketName' => 'fs'
]; ];
if (isset($options['bucketName']) && ! is_string($options['bucketName'])) {
throw new InvalidArgumentTypeException('"bucketName" option', $options['bucketName'], 'string');
}
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
throw new InvalidArgumentTypeException('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
}
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
}
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
}
$this->databaseName = (string) $databaseName; $this->databaseName = (string) $databaseName;
$this->options = $options; $this->options = $options;
$this->collectionsWrapper = new GridFSCollectionsWrapper($manager, $databaseName, $options);
$collectionOptions = array_intersect_key($options, ['readPreference' => 1, 'writeConcern' => 1]);
$this->collectionsWrapper = new GridFSCollectionsWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
$this->registerStreamWrapper($manager); $this->registerStreamWrapper($manager);
} }
/** /**
* Opens a Stream for writing the contents of a file. * Delete a file from the GridFS bucket.
*
* If the files collection document is not found, this method will still
* attempt to delete orphaned chunks.
* *
* @param string $filename file to upload * @param ObjectId $id ObjectId of the file
* @param array $options Stream Options * @throws GridFSFileNotFoundException
* @return Stream uploadStream
*/ */
public function openUploadStream($filename, array $options = []) public function delete(ObjectId $id)
{ {
$options+= ['chunkSizeBytes' => $this->options['chunkSizeBytes']]; $file = $this->collectionsWrapper->getFilesCollection()->findOne(['_id' => $id]);
$streamOptions = [ $this->collectionsWrapper->getChunksCollection()->deleteMany(['files_id' => $id]);
'collectionsWrapper' => $this->collectionsWrapper,
'uploadOptions' => $options if ($file === null) {
]; throw new GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
$context = stream_context_create(['gridfs' => $streamOptions]); }
return fopen(sprintf('gridfs://%s/%s', $this->databaseName, $filename), 'w', false, $context);
$this->collectionsWrapper->getFilesCollection()->deleteOne(['_id' => $id]);
} }
/** /**
* Upload a file to this bucket by specifying the source stream file * Writes the contents of a GridFS file to a writable stream.
* *
* @param String $filename Filename To Insert * @param ObjectId $id ObjectId of the file
* @param Stream $source Source Stream * @param resource $destination Writable Stream
* @param array $options Stream Options * @throws GridFSFileNotFoundException
* @return ObjectId
*/ */
public function uploadFromStream($filename, $source, array $options = []) public function downloadToStream(ObjectId $id, $destination)
{ {
$options+= ['chunkSizeBytes' => $this->options['chunkSizeBytes']]; $file = $this->collectionsWrapper->getFilesCollection()->findOne(
$gridFsStream = new GridFsUpload($this->collectionsWrapper, $filename, $options); ['_id' => $id],
return $gridFsStream->uploadFromStream($source); ['typeMap' => ['root' => 'stdClass']]
);
if ($file === null) {
throw new GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
}
$gridFsStream = new GridFSDownload($this->collectionsWrapper, $file);
$gridFsStream->downloadToStream($destination);
} }
/** /**
* Opens a Stream for reading the contents of a file specified by ID. * Writes the contents of a GridFS file, which is selected by name and
* revision, to a writable stream.
*
* Supported options:
*
* * revision (integer): Which revision (i.e. documents with the same
* filename and different uploadDate) of the file to retrieve. Defaults
* to -1 (i.e. the most recent revision).
*
* Revision numbers are defined as follows:
* *
* @param ObjectId $id * * 0 = the original stored file
* @return Stream * * 1 = the first revision
* * 2 = the second revision
* * etc…
* * -2 = the second most recent revision
* * -1 = the most recent revision
*
* @param string $filename File name
* @param resource $destination Writable Stream
* @param array $options Download options
* @throws GridFSFileNotFoundException
*/ */
public function openDownloadStream(\MongoDB\BSON\ObjectId $id) public function downloadToStreamByName($filename, $destination, array $options = [])
{ {
$file = $this->collectionsWrapper->getFilesCollection()->findOne(['_id' => $id]); $options += ['revision' => -1];
if (is_null($file)) { $file = $this->findFileRevision($filename, $options['revision']);
throw new \MongoDB\Exception\GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace()); $gridFsStream = new GridFSDownload($this->collectionsWrapper, $file);
} $gridFsStream->downloadToStream($destination);
return $this->openDownloadStreamByFile($file);
} }
/**
* Downloads the contents of the stored file specified by id and writes /**
* the contents to the destination Stream. * Find files from the GridFS bucket's files collection.
* @param ObjectId $id GridFS File Id *
* @param Stream $destination Destination Stream * @see Find::__construct() for supported options
*/ * @param array|object $filter Query by which to filter documents
public function downloadToStream(\MongoDB\BSON\ObjectId $id, $destination) * @param array $options Additional options
* @return Cursor
*/
public function find($filter, array $options = [])
{ {
$file = $this->collectionsWrapper->getFilesCollection()->findOne(['_id' => $id]); return $this->collectionsWrapper->getFilesCollection()->find($filter, $options);
if (is_null($file)) {
throw new \MongoDB\Exception\GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
}
$gridFsStream = new GridFsDownload($this->collectionsWrapper, $file);
$gridFsStream->downloadToStream($destination);
} }
public function getCollectionsWrapper()
{
return $this->collectionsWrapper;
}
public function getDatabaseName()
{
return $this->databaseName;
}
/** /**
* Delete a file from the GridFS bucket. If the file collection entry is not found, still attempts to delete orphaned chunks * Gets the ID of the GridFS file associated with a stream.
* *
* @param ObjectId $id file id * @param resource $stream GridFS stream
* @throws GridFSFileNotFoundException * @return mixed
*/ */
public function delete(\MongoDB\BSON\ObjectId $id) public function getIdFromStream($stream)
{ {
$file = $this->collectionsWrapper->getFilesCollection()->findOne(['_id' => $id]); $metadata = stream_get_meta_data($stream);
$this->collectionsWrapper->getChunksCollection()->deleteMany(['files_id' => $id]);
if (is_null($file)) { if (isset($metadata['wrapper_data']->id)) {
throw new \MongoDB\Exception\GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace()); return $metadata['wrapper_data']->id;
} }
$this->collectionsWrapper->getFilesCollection()->deleteOne(['_id' => $id]);
return;
} }
/** /**
* Open a stream to download a file from the GridFS bucket. Searches for the file by the specified name then returns a stream to the specified file * Opens a readable stream for reading a GridFS file.
* @param string $filename name of the file to download *
* @param int $revision the revision of the file to download * @param ObjectId $id ObjectId of the file
* @throws GridFSFileNotFoundException * @return resource
*/ * @throws GridFSFileNotFoundException
public function openDownloadStreamByName($filename, $revision = -1) */
{ public function openDownloadStream(ObjectId $id)
$file = $this->findFileRevision($filename, $revision); {
$file = $this->collectionsWrapper->getFilesCollection()->findOne(
['_id' => $id],
['typeMap' => ['root' => 'stdClass']]
);
if ($file === null) {
throw new GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
}
return $this->openDownloadStreamByFile($file); return $this->openDownloadStreamByFile($file);
} }
/** /**
* Download a file from the GridFS bucket by name. Searches for the file by the specified name then loads data into the stream * Opens a readable stream stream to read a GridFS file, which is selected
* * by name and revision.
* @param string $filename name of the file to download *
* @param int $revision the revision of the file to download * Supported options:
* @throws GridFSFileNotFoundException *
*/ * * revision (integer): Which revision (i.e. documents with the same
public function downloadToStreamByName($filename, $destination, $revision=-1) * filename and different uploadDate) of the file to retrieve. Defaults
{ * to -1 (i.e. the most recent revision).
$file = $this->findFileRevision($filename, $revision); *
$gridFsStream = new GridFsDownload($this->collectionsWrapper, $file); * Revision numbers are defined as follows:
$gridFsStream->downloadToStream($destination); *
} * * 0 = the original stored file
/** * * 1 = the first revision
* Find files from the GridFS bucket files collection. * * 2 = the second revision
* * * etc…
* @param array $filter filter to find by * * -2 = the second most recent revision
* @param array $options options to * * -1 = the most recent revision
*/ *
public function find($filter, array $options =[]) * @param string $filename File name
* @param array $options Download options
* @return resource
* @throws GridFSFileNotFoundException
*/
public function openDownloadStreamByName($filename, array $options = [])
{ {
return $this->collectionsWrapper->getFilesCollection()->find($filter, $options); $options += ['revision' => -1];
$file = $this->findFileRevision($filename, $options['revision']);
return $this->openDownloadStreamByFile($file);
} }
/** /**
* Gets the id of the GridFs file associated with $stream * Opens a writable stream for writing a GridFS file.
* *
* @param resource $stream wrapped gridFsStream * Supported options:
*/ *
public function getIdFromStream($stream) * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
* bucket's chunk size.
*
* @param string $filename File name
* @param array $options Stream options
* @return resource
*/
public function openUploadStream($filename, array $options = [])
{ {
$metadata = stream_get_meta_data($stream); $options += ['chunkSizeBytes' => $this->options['chunkSizeBytes']];
if(isset($metadata["wrapper_data"]->id)){
return $metadata["wrapper_data"]->id; $streamOptions = [
} 'collectionsWrapper' => $this->collectionsWrapper,
return null; 'uploadOptions' => $options,
];
$context = stream_context_create(['gridfs' => $streamOptions]);
return fopen(sprintf('gridfs://%s/%s', $this->databaseName, $filename), 'w', false, $context);
} }
/** /**
* Gets the id of the GridFs file associated with $stream * Renames the GridFS file with the specified ID.
* *
* @param \MongoDB\BSON\ObjectId $id id of the file to rename * @param ObjectId $id ID of the file to rename
* @param string $newFilename new name for the file * @param string $newFilename New file name
* @throws \MongoDB\Exception\GridFSFileNotFoundException * @throws GridFSFileNotFoundException
*/ */
public function rename(\MongoDB\BSON\ObjectId $id, $newFilename) public function rename(ObjectId $id, $newFilename)
{ {
$filesCollection = $this->collectionsWrapper->getFilesCollection(); $filesCollection = $this->collectionsWrapper->getFilesCollection();
$file = $filesCollection->findOne(["_id" => $id]); $file = $filesCollection->findOne(['_id' => $id]);
if (is_null($file)) {
throw new \MongoDB\Exception\GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace()); if ($file === null) {
throw new GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
} }
$file->filename = $newFilename; $file->filename = $newFilename;
$filesCollection->replaceOne(["_id"=> $id], $file); $filesCollection->replaceOne(['_id' => $id], $file);
}
public function getCollectionsWrapper()
{
return $this->collectionsWrapper;
} }
public function getDatabaseName(){
return $this->databaseName; /**
} * Writes the contents of a readable stream to a GridFS file.
private function openDownloadStreamByFile($file) *
* Supported options:
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
* bucket's chunk size.
*
* @param string $filename File name
* @param resource $source Readable stream
* @param array $options Stream options
* @return ObjectId
*/
public function uploadFromStream($filename, $source, array $options = [])
{ {
$options = ['collectionsWrapper' => $this->collectionsWrapper, $options += ['chunkSizeBytes' => $this->options['chunkSizeBytes']];
'file' => $file $gridFsStream = new GridFSUpload($this->collectionsWrapper, $filename, $options);
];
$context = stream_context_create(['gridfs' => $options]); return $gridFsStream->uploadFromStream($source);
return fopen(sprintf('gridfs://%s/%s', $this->databaseName, $file->filename), 'r', false, $context);
} }
private function findFileRevision($filename, $revision) private function findFileRevision($filename, $revision)
{ {
if ($revision < 0) { if ($revision < 0) {
$skip = abs($revision) -1; $skip = abs($revision) - 1;
$sortOrder = -1; $sortOrder = -1;
} else { } else {
$skip = $revision; $skip = $revision;
$sortOrder = 1; $sortOrder = 1;
} }
$filesCollection = $this->collectionsWrapper->getFilesCollection(); $filesCollection = $this->collectionsWrapper->getFilesCollection();
$file = $filesCollection->findOne(["filename"=> $filename], ["sort" => ["uploadDate"=> $sortOrder], "limit"=>1, "skip" => $skip]); $file = $filesCollection->findOne(
if(is_null($file)) { ['filename' => $filename],
throw new \MongoDB\Exception\GridFSFileNotFoundException($filename, $filesCollection->getNameSpace()); [
'skip' => $skip,
'sort' => ['uploadDate' => $sortOrder],
'typeMap' => ['root' => 'stdClass'],
]
);
if ($file === null) {
throw new GridFSFileNotFoundException($filename, $filesCollection->getNameSpace());
} }
return $file; return $file;
} }
private function registerStreamWrapper($manager)
private function openDownloadStreamByFile($file)
{ {
if(isset(Bucket::$streamWrapper)){ $options = [
'collectionsWrapper' => $this->collectionsWrapper,
'file' => $file,
];
$context = stream_context_create(['gridfs' => $options]);
return fopen(sprintf('gridfs://%s/%s', $this->databaseName, $file->filename), 'r', false, $context);
}
private function registerStreamWrapper(Manager $manager)
{
if (isset(self::$streamWrapper)) {
return; return;
} }
Bucket::$streamWrapper = new \MongoDB\GridFS\StreamWrapper();
Bucket::$streamWrapper->register($manager); self::$streamWrapper = new StreamWrapper();
self::$streamWrapper->register($manager);
} }
} }
<?php <?php
namespace MongoDB\GridFS; namespace MongoDB\GridFS;
use MongoDB\Collection; use MongoDB\Collection;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference; use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern; use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Manager;
use MongoDB\Exception\InvalidArgumentTypeException; use MongoDB\Exception\InvalidArgumentTypeException;
/** /**
* @internal
* GridFSCollectionsWrapper abstracts the GridFS files and chunks collections. * GridFSCollectionsWrapper abstracts the GridFS files and chunks collections.
*
* @internal
*/ */
class GridFSCollectionsWrapper class GridFSCollectionsWrapper
{ {
private $filesCollection;
private $chunksCollection; private $chunksCollection;
private $ensuredIndexes = false; private $ensuredIndexes = false;
private $filesCollection;
/** /**
* Constructs a GridFS bucket. * Constructs a GridFS collection wrapper.
*
* Supported options:
*
* * bucketName (string): The bucket name, which will be used as a prefix
* for the files and chunks collections. Defaults to "fs".
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
* 261120 (i.e. 255 KiB).
* *
* * readPreference (MongoDB\Driver\ReadPreference): Read preference. * @see Collection::__construct() for supported options
* * @param Manager $manager Manager instance from the driver
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * @param string $databaseName Database name
* * @param string $bucketName Bucket name
* @param Manager $manager Manager instance from the driver * @param array $collectionOptions Collection options
* @param string $databaseName Database name
* @param array $options Bucket options
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function __construct(Manager $manager, $databaseName, $options) public function __construct(Manager $manager, $databaseName, $bucketName, array $collectionOptions = [])
{ {
$collectionOptions = []; $this->filesCollection = new Collection($manager, sprintf('%s.%s.files', $databaseName, $bucketName), $collectionOptions);
$this->chunksCollection = new Collection($manager, sprintf('%s.%s.chunks', $databaseName, $bucketName), $collectionOptions);
if (isset($options['bucketName']) && ! is_string($options['bucketName'])) {
throw new InvalidArgumentTypeException('"bucketName" option', $options['bucketName'], 'string');
}
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
throw new InvalidArgumentTypeException('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
}
if (isset($options['readPreference'])) {
if (! $options['readPreference'] instanceof ReadPreference) {
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
} else {
$collectionOptions['readPreference'] = $options['readPreference'];
}
}
if (isset($options['writeConcern'])) {
if (! $options['writeConcern'] instanceof WriteConcern) {
throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
} else {
$collectionOptions['writeConcern'] = $options['writeConcern'];
}
}
$this->filesCollection = new Collection(
$manager,
sprintf('%s.%s.files', $databaseName, $options['bucketName']),
$collectionOptions
);
$this->chunksCollection = new Collection(
$manager,
sprintf('%s.%s.chunks', $databaseName, $options['bucketName']),
$collectionOptions
);
} }
public function chunkInsert($toUpload)
{
$this->ensureIndexes();
$this->chunksCollection->insertOne($toUpload);
}
public function fileInsert($toUpload)
{
$this->ensureIndexes();
$this->filesCollection->insertOne($toUpload);
}
public function getChunksCollection() public function getChunksCollection()
{ {
return $this->chunksCollection; return $this->chunksCollection;
} }
public function getFilesCollection() public function getFilesCollection()
{ {
return $this->filesCollection; return $this->filesCollection;
} }
public function insertChunk($chunk)
{
$this->ensureIndexes();
$this->chunksCollection->insertOne($chunk);
}
private function ensureIndexes() public function insertFile($file)
{ {
if ($this->ensuredIndexes) { $this->ensureIndexes();
return; $this->filesCollection->insertOne($file);
}
if ( ! $this->isFilesCollectionEmpty()) {
return;
}
$this->ensureFilesIndex();
$this->ensureChunksIndex();
$this->ensuredIndexes = true;
} }
private function ensureChunksIndex() private function ensureChunksIndex()
{ {
foreach ($this->chunksCollection->listIndexes() as $index) { foreach ($this->chunksCollection->listIndexes() as $index) {
...@@ -110,8 +64,10 @@ class GridFSCollectionsWrapper ...@@ -110,8 +64,10 @@ class GridFSCollectionsWrapper
return; return;
} }
} }
$this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]); $this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
} }
private function ensureFilesIndex() private function ensureFilesIndex()
{ {
foreach ($this->filesCollection->listIndexes() as $index) { foreach ($this->filesCollection->listIndexes() as $index) {
...@@ -119,8 +75,25 @@ class GridFSCollectionsWrapper ...@@ -119,8 +75,25 @@ class GridFSCollectionsWrapper
return; return;
} }
} }
$this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]); $this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
} }
private function ensureIndexes()
{
if ($this->ensuredIndexes) {
return;
}
if ( ! $this->isFilesCollectionEmpty()) {
return;
}
$this->ensureFilesIndex();
$this->ensureChunksIndex();
$this->ensuredIndexes = true;
}
private function isFilesCollectionEmpty() private function isFilesCollectionEmpty()
{ {
return null === $this->filesCollection->findOne([], [ return null === $this->filesCollection->findOne([], [
......
<?php <?php
namespace MongoDB\GridFS; namespace MongoDB\GridFS;
use MongoDB\Collection; use MongoDB\Driver\Exception\Exception;
use \MongoDB\Exception\GridFSCorruptFileException; use MongoDB\Exception\GridFSCorruptFileException;
use \MongoDB\Exception\InvalidArgumentTypeException; use stdClass;
/** /**
* GridFSDownload abstracts the process of reading a GridFS file.
*
* @internal * @internal
* GridFSDownload abstracts the processes of downloading from a GridFSBucket
*/ */
class GridFsDownload class GridFSDownload
{ {
private $buffer;
private $bufferEmpty = true;
private $bufferFresh = true;
private $bytesSeen = 0;
private $chunkOffset = 0;
private $chunksIterator; private $chunksIterator;
private $bytesSeen=0;
private $numChunks;
private $iteratorEmpty=false;
private $firstCheck=true;
private $bufferFresh=true;
private $bufferEmpty=true;
private $collectionsWrapper; private $collectionsWrapper;
private $chunkOffset = 0;
private $buffer;
private $file; private $file;
private $firstCheck = true;
private $iteratorEmpty = false;
private $numChunks;
/** /**
* Constructs a GridFS download stream * Constructs a GridFS download stream.
*
* *
* @param GridFSCollectionsWrapper $collectionsWrapper File options * @param GridFSCollectionsWrapper $collectionsWrapper GridFS collections wrapper
* @param \stdClass $file GridFS file to use * @param stdClass $file GridFS file document
* @throws GridFSCorruptFileException, InvalidArgumentTypeException * @throws GridFSCorruptFileException
*/ */
public function __construct( public function __construct(GridFSCollectionsWrapper $collectionsWrapper, stdClass $file)
GridFSCollectionsWrapper $collectionsWrapper,
$file
)
{ {
if(!($file instanceof \stdClass)){
throw new \MongoDB\Exception\InvalidArgumentTypeException('"file"', $file, 'stdClass');
}
$this->collectionsWrapper = $collectionsWrapper; $this->collectionsWrapper = $collectionsWrapper;
$this->file = $file; $this->file = $file;
try{
$cursor = $this->collectionsWrapper->getChunksCollection()->find(['files_id' => $this->file->_id], ['sort' => ['n' => 1]]); try {
} catch(\MongoDB\Exception $e){ $cursor = $this->collectionsWrapper->getChunksCollection()->find(
throw new \MongoDB\Exception\GridFSCorruptFileException(); ['files_id' => $this->file->_id],
['sort' => ['n' => 1]]
);
} catch (Exception $e) {
// TODO: Why do we replace a driver exception with GridFSCorruptFileException here?
throw new GridFSCorruptFileException();
} }
$this->chunksIterator = new \IteratorIterator($cursor); $this->chunksIterator = new \IteratorIterator($cursor);
if ($this->file->length >= 0) { $this->numChunks = ($file->length >= 0) ? ceil($file->length / $file->chunkSize) : 0;
$this->numChunks = ceil($this->file->length / $this->file->chunkSize);
} else {
$this->numChunks = 0;
}
$this->buffer = fopen('php://temp', 'w+'); $this->buffer = fopen('php://temp', 'w+');
} }
public function downloadToStream($destination)
public function close()
{ {
while($this->advanceChunks()) { fclose($this->buffer);
fwrite($destination, $this->chunksIterator->current()->data->getData());
}
} }
public function downloadNumBytes($numToRead) {
public function downloadNumBytes($numToRead)
{
$output = ""; $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?
$output = fread($this->buffer, $numToRead); $output = fread($this->buffer, $numToRead);
if (strlen($output) == $numToRead) { if (strlen($output) == $numToRead) {
return $output; return $output;
} }
fclose($this->buffer); fclose($this->buffer);
$this->buffer = fopen("php://temp", "w+"); $this->buffer = fopen("php://temp", "w+");
$this->bufferFresh=true; $this->bufferFresh = true;
$this->bufferEmpty=true; $this->bufferEmpty = true;
$bytesLeft = $numToRead - strlen($output); $bytesLeft = $numToRead - strlen($output);
while(strlen($output) < $numToRead && $this->advanceChunks()) {
while (strlen($output) < $numToRead && $this->advanceChunks()) {
$bytesLeft = $numToRead - strlen($output); $bytesLeft = $numToRead - strlen($output);
$output .= substr($this->chunksIterator->current()->data->getData(), 0, $bytesLeft); $output .= substr($this->chunksIterator->current()->data->getData(), 0, $bytesLeft);
} }
if (!$this->iteratorEmpty && $this->file->length > 0 && $bytesLeft < strlen($this->chunksIterator->current()->data->getData())) {
if ( ! $this->iteratorEmpty && $this->file->length > 0 && $bytesLeft < strlen($this->chunksIterator->current()->data->getData())) {
fwrite($this->buffer, substr($this->chunksIterator->current()->data->getData(), $bytesLeft)); fwrite($this->buffer, substr($this->chunksIterator->current()->data->getData(), $bytesLeft));
$this->bufferEmpty=false; $this->bufferEmpty = false;
} }
return $output; return $output;
} }
public function getSize()
public function downloadToStream($destination)
{ {
return $this->file->length; while ($this->advanceChunks()) {
// TODO: Should we be checking for fwrite errors here?
fwrite($destination, $this->chunksIterator->current()->data->getData());
}
} }
public function getFile()
{
return $this->file;
}
public function getId() public function getId()
{ {
return $this->file->_id; return $this->file->_id;
} }
public function getFile()
public function getSize()
{ {
return $this->file; return $this->file->length;
}
public function isEOF()
{
return ($this->iteratorEmpty && $this->bufferEmpty);
} }
private function advanceChunks() private function advanceChunks()
{ {
if($this->chunkOffset >= $this->numChunks) { if ($this->chunkOffset >= $this->numChunks) {
$this->iteratorEmpty=true; $this->iteratorEmpty = true;
return false; return false;
} }
if($this->firstCheck) {
if ($this->firstCheck) {
$this->chunksIterator->rewind(); $this->chunksIterator->rewind();
$this->firstCheck=false; $this->firstCheck = false;
} else { } else {
$this->chunksIterator->next(); $this->chunksIterator->next();
} }
if (!$this->chunksIterator->valid()) {
throw new \MongoDB\Exception\GridFSCorruptFileException(); if ( ! $this->chunksIterator->valid()) {
throw new GridFSCorruptFileException();
} }
if ($this->chunksIterator->current()->n != $this->chunkOffset) { if ($this->chunksIterator->current()->n != $this->chunkOffset) {
throw new \MongoDB\Exception\GridFSCorruptFileException(); throw new GridFSCorruptFileException();
} }
$chunkSizeIs = strlen($this->chunksIterator->current()->data->getData());
if ($this->chunkOffset == $this->numChunks - 1) { $actualChunkSize = strlen($this->chunksIterator->current()->data->getData());
$chunkSizeShouldBe = $this->file->length - $this->bytesSeen;
if($chunkSizeShouldBe != $chunkSizeIs) { $expectedChunkSize = ($this->chunkOffset == $this->numChunks - 1)
throw new \MongoDB\Exception\GridFSCorruptFileException(); ? ($this->file->length - $this->bytesSeen)
} : $this->file->chunkSize;
} else if ($this->chunkOffset < $this->numChunks - 1) {
if($chunkSizeIs != $this->file->chunkSize) { if ($actualChunkSize != $expectedChunkSize) {
throw new \MongoDB\Exception\GridFSCorruptFileException(); throw new GridFSCorruptFileException();
}
} }
$this->bytesSeen+= $chunkSizeIs;
$this->bytesSeen += $actualChunkSize;
$this->chunkOffset++; $this->chunkOffset++;
return true;
}
public function close()
{
fclose($this->buffer);
}
public function isEOF() return true;
{
$eof = $this->iteratorEmpty && $this->bufferEmpty;
return $eof;
} }
} }
<?php <?php
namespace MongoDB\GridFS; namespace MongoDB\GridFS;
use MongoDB\Collection; use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Driver\Exception\Exception;
use MongoDB\Exception\InvalidArgumentTypeException; use MongoDB\Exception\InvalidArgumentTypeException;
use MongoDB\BSON;
/** /**
* GridFSUpload abstracts the process of writing a GridFS file.
*
* @internal * @internal
* GridFsupload abstracts the processes of inserting into a GridFSBucket
*/ */
class GridFsUpload class GridFSUpload
{ {
private $ctx; private $buffer;
private $bufferLength = 0; private $bufferLength = 0;
private $indexChecker;
private $length = 0;
private $collectionsWrapper;
private $chunkOffset = 0; private $chunkOffset = 0;
private $chunkSize; private $chunkSize;
private $buffer; private $collectionsWrapper;
private $ctx;
private $file; private $file;
private $indexChecker;
private $isClosed = false; private $isClosed = false;
private $length = 0;
/** /**
* Constructs a GridFS upload stream * Constructs a GridFS upload stream.
* *
* Supported options: * Supported options:
* *
* * contentType (string): DEPRECATED content type to be stored with the file. * * aliases (array of strings): DEPRECATED An array of aliases.
* This information should now be added to the metadata * Applications wishing to store aliases should add an aliases field to
* the metadata document instead.
* *
* * aliases (array of strings): DEPRECATED An array of aliases. * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
* Applications wishing to store aliases should add an aliases field to the * 261120 (i.e. 255 KiB).
* metadata document instead.
*
* * metadata (array or object): User data for the 'metadata' field of the files
* collection document.
* *
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * contentType (string): DEPRECATED content type to be stored with the
* file. This information should now be added to the metadata.
* *
* * chunkSizeBytes: size of each chunk * * metadata (document): User data for the "metadata" field of the files
* collection document.
* *
* @param GridFSCollectionsWrapper $collectionsWrapper Files Collection * @param GridFSCollectionsWrapper $collectionsWrapper GridFS collections wrapper
* @param string $filename Filename to insert * @param string $filename File name
* @param array $options File options * @param array $options Upload options
* @throws InvalidArgumentTypeException * @throws InvalidArgumentTypeException
*/ */
public function __construct( public function __construct(GridFSCollectionsWrapper $collectionsWrapper, $filename, array $options = [])
GridFsCollectionsWrapper $collectionsWrapper,
$filename,
array $options=[]
)
{ {
$this->ctx = hash_init('md5'); $options += ['chunkSizeBytes' => 261120];
$this->collectionsWrapper = $collectionsWrapper;
$this->buffer = fopen('php://temp', 'w+');
$options +=['chunkSizeBytes' => 261120]; if (isset($options['aliases']) && ! \MongoDB\is_string_array($options['aliases'])) {
$this->chunkSize = $options['chunkSizeBytes']; throw new InvalidArgumentTypeException('"aliases" option', $options['aliases'], 'array of strings');
$time = $this->millitime();
$uploadDate = new \MongoDB\BSON\UTCDateTime($time);
$objectId = new \MongoDB\BSON\ObjectId();
$main_file = [
"chunkSize" => $this->chunkSize,
"filename" => $filename,
"uploadDate" => $uploadDate,
"_id" => $objectId
];
$fileOptions = [];
if (isset($options['contentType'])) {
if (is_string($options['contentType'])) {
$fileOptions['contentType'] = $options['contentType'];
} else {
throw new \MongoDB\Exception\InvalidArgumentTypeException('"contentType" option', $options['contentType'], 'string');
}
}
if (isset($options['aliases'])) {
if (\MongoDB\is_string_array($options['aliases'])) {
$fileOptions['aliases'] = $options['aliases'];
} else {
throw new \MongoDB\Exception\InvalidArgumentTypeException('"aliases" option', $options['aliases'], 'array of strings');
}
} }
if (isset($options['metadata'])) { if (isset($options['contentType']) && ! is_string($options['contentType'])) {
if (is_array($options['metadata']) || is_object($options['metadata'])) { throw new InvalidArgumentTypeException('"contentType" option', $options['contentType'], 'string');
$fileOptions['metadata'] = $options['metadata'];
} else {
throw new \MongoDB\Exception\InvalidArgumentTypeException('"metadata" option', $options['metadata'], 'object or array');
}
} }
$this->file = array_merge($main_file, $fileOptions);
} if (isset($options['metadata']) && ! is_array($options['metadata']) && ! is_object($options['metadata'])) {
/** throw new InvalidArgumentTypeException('"metadata" option', $options['metadata'], 'array or object');
* Reads data from a stream into GridFS
*
* @param Stream $source Source Stream
* @return ObjectId
*/
public function uploadFromStream($source)
{
if (!is_resource($source) || get_resource_type($source) != "stream") {
throw new UnexpectedTypeException('stream', $source);
} else{
$streamMetadata = stream_get_meta_data($source);
}
while ($data = $this->readChunk($source)) {
$this->insertChunk($data);
}
return $this->fileCollectionInsert();
}
/**
* Insert a chunks into GridFS from a string
*
* @param string $toWrite String to upload
* @return int
*/
public function insertChunks($toWrite)
{
if($this->isClosed){
return;
} }
$readBytes = 0;
while($readBytes != strlen($toWrite)) { $this->chunkSize = $options['chunkSizeBytes'];
$addToBuffer = substr($toWrite, $readBytes, $this->chunkSize - $this->bufferLength); $this->collectionsWrapper = $collectionsWrapper;
fwrite($this->buffer, $addToBuffer); $this->buffer = fopen('php://temp', 'w+');
$readBytes += strlen($addToBuffer); $this->ctx = hash_init('md5');
$this->bufferLength += strlen($addToBuffer);
if($this->bufferLength == $this->chunkSize) { $this->file = [
rewind($this->buffer); '_id' => new ObjectId(),
$this->insertChunk(stream_get_contents($this->buffer)); 'chunkSize' => $this->chunkSize,
ftruncate($this->buffer,0); 'filename' => (string) $filename,
$this->bufferLength = 0; 'uploadDate' => $this->createUploadDate(),
} ] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]);
}
return $readBytes;
} }
/** /**
* Close an active stream, pushes all buffered data to GridFS * Closes an active stream and flushes all buffered data to GridFS.
* */
*/
public function close() public function close()
{ {
if($this->isClosed){ if ($this->isClosed) {
// TODO: Should this be an error condition? e.g. BadMethodCallException
return; return;
} }
rewind($this->buffer); rewind($this->buffer);
$cached = stream_get_contents($this->buffer); $cached = stream_get_contents($this->buffer);
if(strlen($cached) > 0) { if (strlen($cached) > 0) {
$this->insertChunk($cached); $this->insertChunk($cached);
} }
fclose($this->buffer); fclose($this->buffer);
$this->fileCollectionInsert(); $this->fileCollectionInsert();
$this->isClosed = true; $this->isClosed = true;
} }
public function getSize()
public function getChunkSize()
{ {
return $this->length; return $this->chunkSize;
} }
public function getFile()
{
return $this->file;
}
public function getId() public function getId()
{ {
return $this->file["_id"]; return $this->file['_id'];
} }
public function getLength() public function getLength()
{ {
return $this->length; return $this->length;
} }
public function getChunkSize()
public function getSize()
{ {
return $this->chunkSize; return $this->length;
} }
public function getFile()
/**
* Inserts binary data into GridFS via chunks.
*
* Data will be buffered internally until chunkSizeBytes are accumulated, at
* which point a chunk's worth of data will be inserted and the buffer
* reset.
*
* @param string $toWrite Binary data to write
* @return int
*/
public function insertChunks($toWrite)
{ {
return $this->file; if ($this->isClosed) {
// TODO: Should this be an error condition? e.g. BadMethodCallException
return;
}
$readBytes = 0;
while ($readBytes != strlen($toWrite)) {
$addToBuffer = substr($toWrite, $readBytes, $this->chunkSize - $this->bufferLength);
fwrite($this->buffer, $addToBuffer);
$readBytes += strlen($addToBuffer);
$this->bufferLength += strlen($addToBuffer);
if ($this->bufferLength == $this->chunkSize) {
rewind($this->buffer);
$this->insertChunk(stream_get_contents($this->buffer));
ftruncate($this->buffer, 0);
$this->bufferLength = 0;
}
}
return $readBytes;
} }
public function isEOF() public function isEOF()
{ {
return $this->isClosed; return $this->isClosed;
} }
/**
* Writes the contents of a readable stream to a GridFS file.
*
* @param resource $source Readable stream
* @return ObjectId
*/
public function uploadFromStream($source)
{
if ( ! is_resource($source) || get_resource_type($source) != "stream") {
throw new InvalidArgumentTypeException('$stream', $source, 'resource');
}
$streamMetadata = stream_get_meta_data($source);
while ($data = $this->readChunk($source)) {
$this->insertChunk($data);
}
return $this->fileCollectionInsert();
}
private function abort() private function abort()
{ {
$this->collectionsWrapper->getChunksCollection()->deleteMany(["files_id"=> $this->file["_id"]]); $this->collectionsWrapper->getChunksCollection()->deleteMany(['files_id' => $this->file['_id']]);
$this->collectionsWrapper->getFilesCollection()->deleteOne(["_id"=> $this->file['_id']]); $this->collectionsWrapper->getFilesCollection()->deleteOne(['_id' => $this->file['_id']]);
$this->isClosed = true; $this->isClosed = true;
} }
private function insertChunk($data)
// From: http://stackoverflow.com/questions/3656713/how-to-get-current-time-in-milliseconds-in-php
private function createUploadDate()
{ {
if($this->isClosed){ $parts = explode(' ', microtime());
return; $milliseconds = sprintf('%d%03d', $parts[1], $parts[0] * 1000);
}
$toUpload = ["files_id" => $this->file['_id'], "n" => $this->chunkOffset, "data" => new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_GENERIC)]; return new UTCDateTime($milliseconds);
hash_update($this->ctx, $data);
$this->collectionsWrapper->chunkInsert($toUpload);
$this->length += strlen($data);
$this->chunkOffset++;
} }
private function fileCollectionInsert() private function fileCollectionInsert()
{ {
if($this->isClosed){ if ($this->isClosed) {
// TODO: Should this be an error condition? e.g. BadMethodCallException
return; return;
} }
$md5 = hash_final($this->ctx); $md5 = hash_final($this->ctx);
$this->file = array_merge($this->file, ['length' => $this->length, 'md5' => $md5]);
$this->collectionsWrapper->fileInsert($this->file); $this->file['length'] = $this->length;
$this->file['md5'] = $md5;
$this->collectionsWrapper->insertFile($this->file);
return $this->file['_id']; return $this->file['_id'];
} }
//from: http://stackoverflow.com/questions/3656713/how-to-get-current-time-in-milliseconds-in-php
private function millitime() { private function insertChunk($data)
$microtime = microtime(); {
$comps = explode(' ', $microtime); if ($this->isClosed) {
return sprintf('%d%03d', $comps[1], $comps[0] * 1000); // TODO: Should this be an error condition? e.g. BadMethodCallException
return;
}
$toUpload = [
'files_id' => $this->file['_id'],
'n' => $this->chunkOffset,
'data' => new Binary($data, Binary::TYPE_GENERIC),
];
hash_update($this->ctx, $data);
$this->collectionsWrapper->insertChunk($toUpload);
$this->length += strlen($data);
$this->chunkOffset++;
} }
private function readChunk($source) private function readChunk($source)
{ {
$data; try {
try{
$data = fread($source, $this->chunkSize); $data = fread($source, $this->chunkSize);
} catch(Exception $e) { } catch (Exception $e) {
$this->abort(); $this->abort();
throw $e; throw $e;
} }
return $data; return $data;
} }
} }
<?php <?php
namespace MongoDB\GridFS;
use MongoDB\Collection; namespace MongoDB\GridFS;
/** /**
* Stream wrapper for reading and writing a GridFS file. * Stream wrapper for reading and writing a GridFS file.
* *
* @internal * @internal
* @see MongoDB\GridFS\Bucket::openUploadStream(), MongoDB\GridFS\Bucket::openDownloadStream() * @see Bucket::openUploadStream()
* @see Bucket::openDownloadStream()
*/ */
class StreamWrapper class StreamWrapper
{ {
public $context; public $context;
private $filename;
private $protocol = 'gridfs';
private $mode;
private $gridFsStream;
private $collectionsWrapper;
public $id; public $id;
private $collectionsWrapper;
private $gridFSStream;
private $mode;
public function openReadStream()
{
$context = stream_context_get_options($this->context);
$this->gridFSStream = new GridFSDownload($this->collectionsWrapper, $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->collectionsWrapper, $this->identifier, $options);
$this->id = $this->gridFSStream->getId();
return true;
}
/** /**
* Register the GridFS stream wrapper. * Register the GridFS stream wrapper.
*/ */
...@@ -27,63 +45,59 @@ class StreamWrapper ...@@ -27,63 +45,59 @@ class StreamWrapper
if (in_array('gridfs', stream_get_wrappers())) { if (in_array('gridfs', stream_get_wrappers())) {
stream_wrapper_unregister('gridfs'); stream_wrapper_unregister('gridfs');
} }
stream_wrapper_register('gridfs', get_called_class(), STREAM_IS_URL);
} stream_wrapper_register('gridfs', get_called_class(), \STREAM_IS_URL);
public function stream_write($data)
{
$this->gridFsStream->insertChunks($data);
return strlen($data);
}
public function stream_read($count)
{
return $this->gridFsStream->downloadNumBytes($count);
}
public function stream_eof()
{
return $this->gridFsStream->isEOF();
} }
public function stream_close() public function stream_close()
{ {
$this->gridFsStream->close(); $this->gridFSStream->close();
} }
public function stream_stat()
public function stream_eof()
{ {
$stat = $this->getStatTemplate(); return $this->gridFSStream->isEOF();
$stat[7] = $stat['size'] = $this->gridFsStream->getSize();
return $stat;
} }
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); $context = stream_context_get_options($this->context);
$this->collectionsWrapper =$context['gridfs']['collectionsWrapper']; $this->collectionsWrapper = $context['gridfs']['collectionsWrapper'];
$this->mode = $mode; $this->mode = $mode;
switch ($this->mode) { switch ($this->mode) {
case 'w' : return $this ->openWriteStream(); case 'r': return $this->openReadStream();
case 'r' : return $this ->openReadStream(); case 'w': return $this->openWriteStream();
default: return false; default: return false;
} }
} }
public function openWriteStream() {
$context = stream_context_get_options($this->context); public function stream_read($count)
$options =$context['gridfs']['uploadOptions']; {
$this->gridFsStream = new GridFsUpload($this->collectionsWrapper, $this->identifier, $options); return $this->gridFSStream->downloadNumBytes($count);
$this->id = $this->gridFsStream->getId();
return true;
} }
public function openReadStream() { public function stream_stat()
$context = stream_context_get_options($this->context); {
$this->gridFsStream = new GridFsDownload($this->collectionsWrapper, $context['gridfs']['file']); $stat = $this->getStatTemplate();
$this->id = $this->gridFsStream->getId(); $stat[7] = $stat['size'] = $this->gridFSStream->getSize();
return true;
return $stat;
}
public function stream_write($data)
{
$this->gridFSStream->insertChunks($data);
return strlen($data);
} }
/** /**
* Gets a URL stat template with default values * Gets a URL stat template with default values
* from https://github.com/aws/aws-sdk-php/blob/master/src/S3/StreamWrapper.php * from https://github.com/aws/aws-sdk-php/blob/master/src/S3/StreamWrapper.php
* @return array * @return array
*/ */
private function getStatTemplate() private function getStatTemplate()
{ {
return [ return [
...@@ -102,10 +116,10 @@ class StreamWrapper ...@@ -102,10 +116,10 @@ class StreamWrapper
12 => -1, 'blocks' => -1, 12 => -1, 'blocks' => -1,
]; ];
} }
private function initProtocol($path) private function initProtocol($path)
{ {
$parsed_path = parse_url($path); $parsed_path = parse_url($path);
$this->databaseName = $parsed_path["host"]; $this->identifier = substr($parsed_path['path'], 1);
$this->identifier = substr($parsed_path["path"], 1);
} }
} }
...@@ -176,25 +176,25 @@ class BucketFunctionalTest extends FunctionalTestCase ...@@ -176,25 +176,25 @@ class BucketFunctionalTest extends FunctionalTestCase
$this->bucket->uploadFromStream("test",$this->generateStream("bar")); $this->bucket->uploadFromStream("test",$this->generateStream("bar"));
$this->bucket->uploadFromStream("test",$this->generateStream("baz")); $this->bucket->uploadFromStream("test",$this->generateStream("baz"));
$this->assertEquals("foo", stream_get_contents($this->bucket->openDownloadStreamByName("test", 0))); $this->assertEquals("foo", stream_get_contents($this->bucket->openDownloadStreamByName("test", ['revision' => 0])));
$this->assertEquals("bar", stream_get_contents($this->bucket->openDownloadStreamByName("test", 1))); $this->assertEquals("bar", stream_get_contents($this->bucket->openDownloadStreamByName("test", ['revision' => 1])));
$this->assertEquals("baz", stream_get_contents($this->bucket->openDownloadStreamByName("test", 2))); $this->assertEquals("baz", stream_get_contents($this->bucket->openDownloadStreamByName("test", ['revision' => 2])));
$this->assertEquals("baz", stream_get_contents($this->bucket->openDownloadStreamByName("test", -1))); $this->assertEquals("baz", stream_get_contents($this->bucket->openDownloadStreamByName("test", ['revision' => -1])));
$this->assertEquals("bar", stream_get_contents($this->bucket->openDownloadStreamByName("test", -2))); $this->assertEquals("bar", stream_get_contents($this->bucket->openDownloadStreamByName("test", ['revision' => -2])));
$this->assertEquals("foo", stream_get_contents($this->bucket->openDownloadStreamByName("test", -3))); $this->assertEquals("foo", stream_get_contents($this->bucket->openDownloadStreamByName("test", ['revision' => -3])));
$fileNotFound = '\MongoDB\Exception\GridFSFileNotFoundException'; $fileNotFound = '\MongoDB\Exception\GridFSFileNotFoundException';
$error = null; $error = null;
try{ try{
$this->bucket->openDownloadStreamByName("test", 3); $this->bucket->openDownloadStreamByName("test", ['revision' => 3]);
} catch(\MongoDB\Exception\Exception $e) { } catch(\MongoDB\Exception\Exception $e) {
$error = $e; $error = $e;
} }
$this->assertTrue($error instanceof $fileNotFound); $this->assertTrue($error instanceof $fileNotFound);
$error = null; $error = null;
try{ try{
$this->bucket->openDownloadStreamByName("test", -4); $this->bucket->openDownloadStreamByName("test", ['revision' => -4]);
} catch(\MongoDB\Exception\Exception $e) { } catch(\MongoDB\Exception\Exception $e) {
$error = $e; $error = $e;
} }
......
...@@ -7,12 +7,12 @@ use MongoDB\GridFS; ...@@ -7,12 +7,12 @@ use MongoDB\GridFS;
/** /**
* Functional tests for the Bucket class. * Functional tests for the Bucket class.
*/ */
class GridFsStreamTest extends FunctionalTestCase class GridFSStreamTest extends FunctionalTestCase
{ {
public function testBasic() public function testBasic()
{ {
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test"); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test");
$upload->insertChunks("hello world"); $upload->insertChunks("hello world");
$id = $upload->getId(); $id = $upload->getId();
$upload->close(); $upload->close();
...@@ -22,7 +22,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -22,7 +22,7 @@ class GridFsStreamTest extends FunctionalTestCase
$file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id"=>$id]); $file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id"=>$id]);
$download = new \MongoDB\GridFS\GridFsDownload($this->collectionsWrapper, $file); $download = new \MongoDB\GridFS\GridFSDownload($this->collectionsWrapper, $file);
$stream = fopen('php://temp', 'w+'); $stream = fopen('php://temp', 'w+');
$download->downloadToStream($stream); $download->downloadToStream($stream);
rewind($stream); rewind($stream);
...@@ -31,7 +31,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -31,7 +31,7 @@ class GridFsStreamTest extends FunctionalTestCase
fclose($stream); fclose($stream);
#make sure it's still there! #make sure it's still there!
$download = new \MongoDB\GridFS\GridFsDownload($this->collectionsWrapper, $file); $download = new \MongoDB\GridFS\GridFSDownload($this->collectionsWrapper, $file);
$stream = fopen('php://temp', 'w+'); $stream = fopen('php://temp', 'w+');
$download->downloadToStream($stream); $download->downloadToStream($stream);
rewind($stream); rewind($stream);
...@@ -39,7 +39,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -39,7 +39,7 @@ class GridFsStreamTest extends FunctionalTestCase
$this->assertEquals("hello world", $contents); $this->assertEquals("hello world", $contents);
fclose($stream); fclose($stream);
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test"); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test");
$id = $upload->getId(); $id = $upload->getId();
$upload->close(); $upload->close();
...@@ -47,7 +47,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -47,7 +47,7 @@ class GridFsStreamTest extends FunctionalTestCase
$this->assertEquals(1, $this->collectionsWrapper->getChunksCollection()->count()); $this->assertEquals(1, $this->collectionsWrapper->getChunksCollection()->count());
$file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id"=>$id]); $file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id"=>$id]);
$download = new \MongoDB\GridFS\GridFsDownload($this->collectionsWrapper, $file); $download = new \MongoDB\GridFS\GridFSDownload($this->collectionsWrapper, $file);
$stream = fopen('php://temp', 'w+'); $stream = fopen('php://temp', 'w+');
$download->downloadToStream($stream); $download->downloadToStream($stream);
rewind($stream); rewind($stream);
...@@ -58,7 +58,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -58,7 +58,7 @@ class GridFsStreamTest extends FunctionalTestCase
public function testMd5() public function testMd5()
{ {
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test"); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test");
$upload->insertChunks("hello world\n"); $upload->insertChunks("hello world\n");
$id = $upload->getId(); $id = $upload->getId();
$upload->close(); $upload->close();
...@@ -68,7 +68,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -68,7 +68,7 @@ class GridFsStreamTest extends FunctionalTestCase
} }
public function testUploadDefaultOpts() public function testUploadDefaultOpts()
{ {
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test"); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test");
$this->assertTrue($upload->getId() instanceof \MongoDB\BSON\ObjectId); $this->assertTrue($upload->getId() instanceof \MongoDB\BSON\ObjectId);
$this->assertTrue($upload->getFile()["uploadDate"] instanceof \MongoDB\BSON\UTCDateTime); $this->assertTrue($upload->getFile()["uploadDate"] instanceof \MongoDB\BSON\UTCDateTime);
...@@ -89,7 +89,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -89,7 +89,7 @@ class GridFsStreamTest extends FunctionalTestCase
"aliases" => ["foo", "bar"], "aliases" => ["foo", "bar"],
"metadata" => ["foo" => 1, "bar" => 2] "metadata" => ["foo" => 1, "bar" => 2]
]; ];
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test", $options); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test", $options);
$this->assertEquals($upload->getChunkSize(), 1); $this->assertEquals($upload->getChunkSize(), 1);
$this->assertEquals($upload->getFile()["contentType"], "text/html"); $this->assertEquals($upload->getFile()["contentType"], "text/html");
$this->assertEquals($upload->getFile()["aliases"], ["foo", "bar"]); $this->assertEquals($upload->getFile()["aliases"], ["foo", "bar"]);
...@@ -97,11 +97,11 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -97,11 +97,11 @@ class GridFsStreamTest extends FunctionalTestCase
} }
public function testDownloadDefaultOpts() public function testDownloadDefaultOpts()
{ {
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test"); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test");
$upload->close(); $upload->close();
$file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id" => $upload->getId()]); $file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id" => $upload->getId()]);
$download = new \MongoDB\GridFS\GridFsDownload($this->collectionsWrapper, $file); $download = new \MongoDB\GridFS\GridFSDownload($this->collectionsWrapper, $file);
$download->close(); $download->close();
$this->assertEquals($upload->getId(), $download->getId()); $this->assertEquals($upload->getId(), $download->getId());
...@@ -120,12 +120,12 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -120,12 +120,12 @@ class GridFsStreamTest extends FunctionalTestCase
"aliases" => ["foo", "bar"], "aliases" => ["foo", "bar"],
"metadata" => ["foo" => 1, "bar" => 2] "metadata" => ["foo" => 1, "bar" => 2]
]; ];
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test", $options); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test", $options);
$upload->insertChunks("hello world"); $upload->insertChunks("hello world");
$upload->close(); $upload->close();
$file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id" => $upload->getId()]); $file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id" => $upload->getId()]);
$download = new \MongoDB\GridFS\GridFsDownload($this->collectionsWrapper, $file); $download = new \MongoDB\GridFS\GridFSDownload($this->collectionsWrapper, $file);
$this->assertEquals("test", $download->getFile()->filename); $this->assertEquals("test", $download->getFile()->filename);
$this->assertEquals($upload->getId(), $download->getId()); $this->assertEquals($upload->getId(), $download->getId());
...@@ -141,7 +141,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -141,7 +141,7 @@ class GridFsStreamTest extends FunctionalTestCase
*/ */
public function testInsertChunks($data) public function testInsertChunks($data)
{ {
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test"); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test");
$upload->insertChunks($data); $upload->insertChunks($data);
$upload->close(); $upload->close();
$stream = $this->bucket->openDownloadStream($upload->getId()); $stream = $this->bucket->openDownloadStream($upload->getId());
...@@ -154,7 +154,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -154,7 +154,7 @@ class GridFsStreamTest extends FunctionalTestCase
for($i=0; $i<255*1024+1000; $i++){ for($i=0; $i<255*1024+1000; $i++){
$toUpload .= "a"; $toUpload .= "a";
} }
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test"); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test");
$upload->insertChunks($toUpload); $upload->insertChunks($toUpload);
$upload->close(); $upload->close();
...@@ -170,7 +170,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -170,7 +170,7 @@ class GridFsStreamTest extends FunctionalTestCase
public function testSmallChunks($data) public function testSmallChunks($data)
{ {
$options = ["chunkSizeBytes"=>1]; $options = ["chunkSizeBytes"=>1];
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test", $options); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test", $options);
$upload->insertChunks($data); $upload->insertChunks($data);
$upload->close(); $upload->close();
...@@ -182,11 +182,11 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -182,11 +182,11 @@ class GridFsStreamTest extends FunctionalTestCase
} }
public function testMultipleReads() public function testMultipleReads()
{ {
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test", ["chunkSizeBytes"=>3]); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test", ["chunkSizeBytes"=>3]);
$upload->insertChunks("hello world"); $upload->insertChunks("hello world");
$upload->close(); $upload->close();
$file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id"=>$upload->getId()]); $file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id"=>$upload->getId()]);
$download = new \MongoDB\GridFS\GridFsDownload($this->collectionsWrapper, $file); $download = new \MongoDB\GridFS\GridFSDownload($this->collectionsWrapper, $file);
$this->assertEquals("he", $download->downloadNumBytes(2)); $this->assertEquals("he", $download->downloadNumBytes(2));
$this->assertEquals("ll", $download->downloadNumBytes(2)); $this->assertEquals("ll", $download->downloadNumBytes(2));
$this->assertEquals("o ", $download->downloadNumBytes(2)); $this->assertEquals("o ", $download->downloadNumBytes(2));
...@@ -202,11 +202,11 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -202,11 +202,11 @@ class GridFsStreamTest extends FunctionalTestCase
*/ */
public function testProvidedMultipleReads($data) public function testProvidedMultipleReads($data)
{ {
$upload = new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper, "test", ["chunkSizeBytes"=>rand(1, 5)]); $upload = new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper, "test", ["chunkSizeBytes"=>rand(1, 5)]);
$upload->insertChunks($data); $upload->insertChunks($data);
$upload->close(); $upload->close();
$file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id"=>$upload->getId()]); $file = $this->collectionsWrapper->getFilesCollection()->findOne(["_id"=>$upload->getId()]);
$download = new \MongoDB\GridFS\GridFsDownload($this->collectionsWrapper, $file); $download = new \MongoDB\GridFS\GridFSDownload($this->collectionsWrapper, $file);
$readPos = 0; $readPos = 0;
while($readPos < strlen($data)){ while($readPos < strlen($data)){
...@@ -227,7 +227,7 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -227,7 +227,7 @@ class GridFsStreamTest extends FunctionalTestCase
*/ */
public function testUploadConstructorOptionTypeChecks(array $options) public function testUploadConstructorOptionTypeChecks(array $options)
{ {
new \MongoDB\GridFS\GridFsUpload($this->collectionsWrapper,"test", $options); new \MongoDB\GridFS\GridFSUpload($this->collectionsWrapper,"test", $options);
} }
public function provideInvalidUploadConstructorOptions() public function provideInvalidUploadConstructorOptions()
...@@ -248,22 +248,4 @@ class GridFsStreamTest extends FunctionalTestCase ...@@ -248,22 +248,4 @@ class GridFsStreamTest extends FunctionalTestCase
} }
return $options; return $options;
} }
/**
* @expectedException \MongoDB\Exception\InvalidArgumentTypeException
* @dataProvider provideInvalidDownloadConstructorFile
*/
public function testDownloadConstructorFileCheck($file)
{
$download = new \MongoDB\GridFS\GridFsDownload($this->collectionsWrapper, $file);
}
public function provideInvalidDownloadConstructorFile()
{
$files = [];
$invalidFiles = [123, 3.14, true, []];
foreach ($invalidFiles as $value) {
$files[][] = $value;
}
return $files;
}
} }
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