You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
8.1 KiB
309 lines
8.1 KiB
2 years ago
|
<?php
|
||
|
/*
|
||
|
* Copyright 2016-2017 MongoDB, Inc.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
namespace MongoDB\GridFS;
|
||
|
|
||
|
use MongoDB\BSON\UTCDateTime;
|
||
|
use Exception;
|
||
|
use stdClass;
|
||
|
|
||
|
/**
|
||
|
* Stream wrapper for reading and writing a GridFS file.
|
||
|
*
|
||
|
* @internal
|
||
|
* @see Bucket::openUploadStream()
|
||
|
* @see Bucket::openDownloadStream()
|
||
|
*/
|
||
|
class StreamWrapper
|
||
|
{
|
||
|
/**
|
||
|
* @var resource|null Stream context (set by PHP)
|
||
|
*/
|
||
|
public $context;
|
||
|
|
||
|
private $mode;
|
||
|
private $protocol;
|
||
|
private $stream;
|
||
|
|
||
|
/**
|
||
|
* Return the stream's file document.
|
||
|
*
|
||
|
* @return stdClass
|
||
|
*/
|
||
|
public function getFile()
|
||
|
{
|
||
|
return $this->stream->getFile();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register the GridFS stream wrapper.
|
||
|
*
|
||
|
* @param string $protocol Protocol to use for stream_wrapper_register()
|
||
|
*/
|
||
|
public static function register($protocol = 'gridfs')
|
||
|
{
|
||
|
if (in_array($protocol, stream_get_wrappers())) {
|
||
|
stream_wrapper_unregister($protocol);
|
||
|
}
|
||
|
|
||
|
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()
|
||
|
{
|
||
|
$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()
|
||
|
{
|
||
|
if ( ! $this->stream instanceof ReadableStream) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
$this->initProtocol($path);
|
||
|
$this->mode = $mode;
|
||
|
|
||
|
if ($mode === 'r') {
|
||
|
return $this->initReadableStream();
|
||
|
}
|
||
|
|
||
|
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 $length Number of bytes to read
|
||
|
* @return string
|
||
|
*/
|
||
|
public function stream_read($length)
|
||
|
{
|
||
|
if ( ! $this->stream instanceof ReadableStream) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
return $this->stream->readBytes($length);
|
||
|
} catch (Exception $e) {
|
||
|
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the current position of the stream.
|
||
|
*
|
||
|
* @see http://php.net/manual/en/streamwrapper.stream-seek.php
|
||
|
* @param integer $offset Stream offset to seek to
|
||
|
* @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
|
||
|
* @return boolean True if the position was updated and false otherwise
|
||
|
*/
|
||
|
public function stream_seek($offset, $whence = \SEEK_SET)
|
||
|
{
|
||
|
$size = $this->stream->getSize();
|
||
|
|
||
|
if ($whence === \SEEK_CUR) {
|
||
|
$offset += $this->stream->tell();
|
||
|
}
|
||
|
|
||
|
if ($whence === \SEEK_END) {
|
||
|
$offset += $size;
|
||
|
}
|
||
|
|
||
|
// WritableStreams are always positioned at the end of the stream
|
||
|
if ($this->stream instanceof WritableStream) {
|
||
|
return $offset === $size;
|
||
|
}
|
||
|
|
||
|
if ($offset < 0 || $offset > $size) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$this->stream->seek($offset);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return information about the stream.
|
||
|
*
|
||
|
* @see http://php.net/manual/en/streamwrapper.stream-stat.php
|
||
|
* @return array
|
||
|
*/
|
||
|
public function stream_stat()
|
||
|
{
|
||
|
$stat = $this->getStatTemplate();
|
||
|
|
||
|
$stat[2] = $stat['mode'] = $this->stream instanceof ReadableStream
|
||
|
? 0100444 // S_IFREG & S_IRUSR & S_IRGRP & S_IROTH
|
||
|
: 0100222; // S_IFREG & S_IWUSR & S_IWGRP & S_IWOTH
|
||
|
$stat[7] = $stat['size'] = $this->stream->getSize();
|
||
|
|
||
|
$file = $this->stream->getFile();
|
||
|
|
||
|
if (isset($file->uploadDate) && $file->uploadDate instanceof UTCDateTime) {
|
||
|
$timestamp = $file->uploadDate->toDateTime()->getTimestamp();
|
||
|
$stat[9] = $stat['mtime'] = $timestamp;
|
||
|
$stat[10] = $stat['ctime'] = $timestamp;
|
||
|
}
|
||
|
|
||
|
if (isset($file->chunkSize) && is_integer($file->chunkSize)) {
|
||
|
$stat[11] = $stat['blksize'] = $file->chunkSize;
|
||
|
}
|
||
|
|
||
|
return $stat;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the current position of the stream.
|
||
|
*
|
||
|
* @see http://php.net/manual/en/streamwrapper.stream-tell.php
|
||
|
* @return integer The current position of the stream
|
||
|
*/
|
||
|
public function stream_tell()
|
||
|
{
|
||
|
return $this->stream->tell();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write bytes to the stream.
|
||
|
*
|
||
|
* @see http://php.net/manual/en/streamwrapper.stream-write.php
|
||
|
* @param string $data Data to write
|
||
|
* @return integer The number of bytes written
|
||
|
*/
|
||
|
public function stream_write($data)
|
||
|
{
|
||
|
if ( ! $this->stream instanceof WritableStream) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
return $this->stream->writeBytes($data);
|
||
|
} catch (Exception $e) {
|
||
|
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a stat template with default values.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
private function getStatTemplate()
|
||
|
{
|
||
|
return [
|
||
|
0 => 0, 'dev' => 0,
|
||
|
1 => 0, 'ino' => 0,
|
||
|
2 => 0, 'mode' => 0,
|
||
|
3 => 0, 'nlink' => 0,
|
||
|
4 => 0, 'uid' => 0,
|
||
|
5 => 0, 'gid' => 0,
|
||
|
6 => -1, 'rdev' => -1,
|
||
|
7 => 0, 'size' => 0,
|
||
|
8 => 0, 'atime' => 0,
|
||
|
9 => 0, 'mtime' => 0,
|
||
|
10 => 0, 'ctime' => 0,
|
||
|
11 => -1, 'blksize' => -1,
|
||
|
12 => -1, 'blocks' => -1,
|
||
|
];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize the protocol from the given path.
|
||
|
*
|
||
|
* @see StreamWrapper::stream_open()
|
||
|
* @param string $path
|
||
|
*/
|
||
|
private function initProtocol($path)
|
||
|
{
|
||
|
$parts = explode('://', $path, 2);
|
||
|
$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;
|
||
|
}
|
||
|
}
|