init
This commit is contained in:
319
vendor/yiisoft/yii2-mongodb/src/file/Download.php
vendored
Normal file
319
vendor/yiisoft/yii2-mongodb/src/file/Download.php
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license http://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yii\mongodb\file;
|
||||
|
||||
use MongoDB\BSON\ObjectID;
|
||||
use Yii;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\base\BaseObject;
|
||||
use yii\helpers\FileHelper;
|
||||
use yii\helpers\StringHelper;
|
||||
|
||||
/**
|
||||
* Download represents the GridFS download operation.
|
||||
*
|
||||
* A `Download` object is usually created by calling [[Collection::get()]] or [[Collection::createDownload()]].
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* ```php
|
||||
* Yii::$app->mongodb->getFileCollection()->createDownload($document['_id'])->toFile('/path/to/file.dat');
|
||||
* ```
|
||||
*
|
||||
* You can use `Download::substr()` to read a specific part of the file:
|
||||
*
|
||||
* ```php
|
||||
* $filePart = Yii::$app->mongodb->getFileCollection()->createDownload($document['_id'])->substr(256, 1024);
|
||||
* ```
|
||||
*
|
||||
* @property string $bytes File content. This property is read-only.
|
||||
* @property \MongoDB\Driver\Cursor $chunkCursor Chuck list cursor. This property is read-only.
|
||||
* @property \Iterator $chunkIterator Chuck cursor iterator. This property is read-only.
|
||||
* @property array $document Document to be downloaded. Note that the type of this property differs in getter
|
||||
* and setter. See [[getDocument()]] and [[setDocument()]] for details.
|
||||
* @property string|null $filename File name. This property is read-only.
|
||||
* @property resource $resource File stream resource. This property is read-only.
|
||||
* @property int $size File size. This property is read-only.
|
||||
*
|
||||
* @author Paul Klimov <klimov.paul@gmail.com>
|
||||
* @since 2.1
|
||||
*/
|
||||
class Download extends BaseObject
|
||||
{
|
||||
/**
|
||||
* @var Collection file collection to be used.
|
||||
*/
|
||||
public $collection;
|
||||
|
||||
/**
|
||||
* @var array|ObjectID document to be downloaded.
|
||||
*/
|
||||
private $_document;
|
||||
/**
|
||||
* @var \MongoDB\Driver\Cursor cursor for the file chunks.
|
||||
*/
|
||||
private $_chunkCursor;
|
||||
/**
|
||||
* @var \Iterator iterator for [[chunkCursor]].
|
||||
*/
|
||||
private $_chunkIterator;
|
||||
/**
|
||||
* @var resource|null
|
||||
*/
|
||||
private $_resource;
|
||||
|
||||
|
||||
/**
|
||||
* @return array document to be downloaded.
|
||||
* @throws InvalidConfigException on invalid document configuration.
|
||||
*/
|
||||
public function getDocument()
|
||||
{
|
||||
if (!is_array($this->_document)) {
|
||||
if (is_scalar($this->_document) || $this->_document instanceof ObjectID) {
|
||||
$document = $this->collection->findOne(['_id' => $this->_document]);
|
||||
if (empty($document)) {
|
||||
throw new InvalidConfigException('Document id=' . $this->_document . ' does not exist at collection "' . $this->collection->getFullName() . '"');
|
||||
}
|
||||
$this->_document = $document;
|
||||
} else {
|
||||
$this->_document = (array)$this->_document;
|
||||
}
|
||||
}
|
||||
return $this->_document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data of the document to be downloaded.
|
||||
* Document can be specified by its ID, in this case its data will be fetched automatically
|
||||
* via extra query.
|
||||
* @param array|ObjectID $document document raw data or document ID.
|
||||
*/
|
||||
public function setDocument($document)
|
||||
{
|
||||
$this->_document = $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the associated file.
|
||||
* @return int file size.
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
$document = $this->getDocument();
|
||||
return isset($document['length']) ? $document['length'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns associated file's filename.
|
||||
* @return string|null file name.
|
||||
*/
|
||||
public function getFilename()
|
||||
{
|
||||
$document = $this->getDocument();
|
||||
return isset($document['filename']) ? $document['filename'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns file chunks read cursor.
|
||||
* @param bool $refresh whether to recreate cursor, if it is already exist.
|
||||
* @return \MongoDB\Driver\Cursor chuck list cursor.
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
public function getChunkCursor($refresh = false)
|
||||
{
|
||||
if ($refresh || $this->_chunkCursor === null) {
|
||||
$file = $this->getDocument();
|
||||
$this->_chunkCursor = $this->collection->getChunkCollection()->find(
|
||||
['files_id' => $file['_id']],
|
||||
[],
|
||||
['sort' => ['n' => 1]]
|
||||
);
|
||||
}
|
||||
return $this->_chunkCursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns iterator for the file chunks cursor.
|
||||
* @param bool $refresh whether to recreate iterator, if it is already exist.
|
||||
* @return \Iterator chuck cursor iterator.
|
||||
*/
|
||||
public function getChunkIterator($refresh = false)
|
||||
{
|
||||
if ($refresh || $this->_chunkIterator === null) {
|
||||
$this->_chunkIterator = new \IteratorIterator($this->getChunkCursor($refresh));
|
||||
$this->_chunkIterator->rewind();
|
||||
}
|
||||
return $this->_chunkIterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves file into the given stream.
|
||||
* @param resource $stream stream, which file should be saved to.
|
||||
* @return int number of written bytes.
|
||||
*/
|
||||
public function toStream($stream)
|
||||
{
|
||||
$bytesWritten = 0;
|
||||
foreach ($this->getChunkCursor() as $chunk) {
|
||||
$bytesWritten += fwrite($stream, $chunk['data']->getData());
|
||||
}
|
||||
return $bytesWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves download to the physical file.
|
||||
* @param string $filename name of the physical file.
|
||||
* @return int number of written bytes.
|
||||
*/
|
||||
public function toFile($filename)
|
||||
{
|
||||
$filename = Yii::getAlias($filename);
|
||||
FileHelper::createDirectory(dirname($filename));
|
||||
return $this->toStream(fopen($filename, 'w+'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string of the bytes in the associated file.
|
||||
* @return string file content.
|
||||
*/
|
||||
public function toString()
|
||||
{
|
||||
$result = '';
|
||||
foreach ($this->getChunkCursor() as $chunk) {
|
||||
$result .= $chunk['data']->getData();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an opened stream resource, which can be used to read file.
|
||||
* Note: each invocation of this method will create new file resource.
|
||||
* @return resource stream resource.
|
||||
*/
|
||||
public function toResource()
|
||||
{
|
||||
$protocol = $this->collection->database->connection->registerFileStreamWrapper();
|
||||
|
||||
$context = stream_context_create([
|
||||
$protocol => [
|
||||
'download' => $this,
|
||||
]
|
||||
]);
|
||||
|
||||
$document = $this->getDocument();
|
||||
$url = "{$protocol}://{$this->collection->database->name}.{$this->collection->prefix}?_id={$document['_id']}";
|
||||
return fopen($url, 'r', false, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return part of a file.
|
||||
* @param int $start reading start position.
|
||||
* If non-negative, the returned string will start at the start'th position in file, counting from zero.
|
||||
* If negative, the returned string will start at the start'th character from the end of file.
|
||||
* @param int $length number of bytes to read.
|
||||
* If given and is positive, the string returned will contain at most length characters beginning from start (depending on the length of file).
|
||||
* If given and is negative, then that many characters will be omitted from the end of file (after the start position has been calculated when a start is negative).
|
||||
* @return string|false the extracted part of file or `false` on failure
|
||||
*/
|
||||
public function substr($start, $length)
|
||||
{
|
||||
$document = $this->getDocument();
|
||||
|
||||
if ($start < 0) {
|
||||
$start = max($document['length'] + $start, 0);
|
||||
}
|
||||
|
||||
if ($start > $document['length']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($length < 0) {
|
||||
$length = $document['length'] - $start + $length;
|
||||
if ($length < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$chunkSize = $document['chunkSize'];
|
||||
|
||||
$startChunkNumber = floor($start / $chunkSize);
|
||||
|
||||
$chunkIterator = $this->getChunkIterator();
|
||||
|
||||
if (!$chunkIterator->valid()) {
|
||||
// invalid iterator state - recreate iterator
|
||||
// unable to use `rewind` due to error "Cursors cannot rewind after starting iteration"
|
||||
$chunkIterator = $this->getChunkIterator(true);
|
||||
}
|
||||
|
||||
if ($chunkIterator->key() > $startChunkNumber) {
|
||||
// unable to go back by iterator
|
||||
// unable to use `rewind` due to error "Cursors cannot rewind after starting iteration"
|
||||
$chunkIterator = $this->getChunkIterator(true);
|
||||
}
|
||||
|
||||
$result = '';
|
||||
|
||||
$chunkDataOffset = $start - $startChunkNumber * $chunkSize;
|
||||
while ($chunkIterator->valid()) {
|
||||
if ($chunkIterator->key() >= $startChunkNumber) {
|
||||
$chunk = $chunkIterator->current();
|
||||
$data = $chunk['data']->getData();
|
||||
|
||||
$readLength = min($chunkSize - $chunkDataOffset, $length);
|
||||
|
||||
$result .= StringHelper::byteSubstr($data, $chunkDataOffset, $readLength);
|
||||
|
||||
$length -= $readLength;
|
||||
if ($length <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$chunkDataOffset = 0;
|
||||
}
|
||||
|
||||
$chunkIterator->next();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Compatibility with `MongoGridFSFile` :
|
||||
|
||||
/**
|
||||
* Alias of [[toString()]] method.
|
||||
* @return string file content.
|
||||
*/
|
||||
public function getBytes()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of [[toFile()]] method.
|
||||
* @param string $filename name of the physical file.
|
||||
* @return int number of written bytes.
|
||||
*/
|
||||
public function write($filename)
|
||||
{
|
||||
return $this->toFile($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns persistent stream resource, which can be used to read file.
|
||||
* @return resource file stream resource.
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
if ($this->_resource === null) {
|
||||
$this->_resource = $this->toResource();
|
||||
}
|
||||
return $this->_resource;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user