StreamWrapper.php 8.16 KB
Newer Older
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * 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.
 */
17

18
namespace MongoDB\GridFS;
19

20
use MongoDB\BSON\UTCDateTime;
21
use Exception;
22
use stdClass;
23

24 25 26 27
/**
 * Stream wrapper for reading and writing a GridFS file.
 *
 * @internal
28 29
 * @see Bucket::openUploadStream()
 * @see Bucket::openDownloadStream()
30 31 32
 */
class StreamWrapper
{
33 34 35
    /**
     * @var resource|null Stream context (set by PHP)
     */
36 37
    public $context;

38
    private $mode;
39 40
    private $protocol;
    private $stream;
41

42 43 44
    /**
     * Return the stream's file document.
     *
45
     * @return stdClass
46 47
     */
    public function getFile()
48
    {
49
        return $this->stream->getFile();
50 51
    }

52 53
    /**
     * Register the GridFS stream wrapper.
54 55
     *
     * @param string $protocol Protocol to use for stream_wrapper_register()
56
     */
57
    public static function register($protocol = 'gridfs')
58
    {
59 60
        if (in_array($protocol, stream_get_wrappers())) {
            stream_wrapper_unregister($protocol);
61
        }
62

63
        stream_wrapper_register($protocol, get_called_class(), \STREAM_IS_URL);
64
    }
65

66 67 68 69 70
    /**
     * Closes the stream.
     *
     * @see http://php.net/manual/en/streamwrapper.stream-close.php
     */
71 72
    public function stream_close()
    {
73
        $this->stream->close();
74
    }
75

76 77 78 79 80 81
    /**
     * Returns whether the file pointer is at the end of the stream.
     *
     * @see http://php.net/manual/en/streamwrapper.stream-eof.php
     * @return boolean
     */
82
    public function stream_eof()
83
    {
84 85 86 87
        if ( ! $this->stream instanceof ReadableStream) {
            return false;
        }

88
        return $this->stream->isEOF();
89
    }
90

91 92 93 94 95 96 97 98
    /**
     * 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
99
     * @return boolean
100
     */
101 102 103 104
    public function stream_open($path, $mode, $options, &$openedPath)
    {
        $this->initProtocol($path);
        $this->mode = $mode;
105

106 107 108 109 110 111
        if ($mode === 'r') {
            return $this->initReadableStream();
        }

        if ($mode === 'w') {
            return $this->initWritableStream();
112
        }
113 114

        return false;
115
    }
116

117 118 119 120 121
    /**
     * 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.
122
     *
123
     * @see http://php.net/manual/en/streamwrapper.stream-read.php
124
     * @param integer $length Number of bytes to read
125
     * @return string
126
     */
127
    public function stream_read($length)
128
    {
129 130 131 132
        if ( ! $this->stream instanceof ReadableStream) {
            return '';
        }

133
        try {
134
            return $this->stream->readBytes($length);
135 136 137 138
        } catch (Exception $e) {
            trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
            return false;
        }
139
    }
140

141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
    /**
     * 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;
    }

175 176 177 178 179 180
    /**
     * Return information about the stream.
     *
     * @see http://php.net/manual/en/streamwrapper.stream-stat.php
     * @return array
     */
181 182 183
    public function stream_stat()
    {
        $stat = $this->getStatTemplate();
184

185 186 187
        $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
188
        $stat[7] = $stat['size'] = $this->stream->getSize();
189

190 191
        $file = $this->stream->getFile();

192 193 194 195 196 197
        if (isset($file->uploadDate) && $file->uploadDate instanceof UTCDateTime) {
            $timestamp = $file->uploadDate->toDateTime()->getTimestamp();
            $stat[9] = $stat['mtime'] = $timestamp;
            $stat[10] = $stat['ctime'] = $timestamp;
        }

198 199 200 201
        if (isset($file->chunkSize) && is_integer($file->chunkSize)) {
            $stat[11] = $stat['blksize'] = $file->chunkSize;
        }

202 203 204
        return $stat;
    }

205 206 207 208 209 210 211 212 213 214 215
    /**
     * 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();
    }

216 217 218 219 220
    /**
     * Write bytes to the stream.
     *
     * @see http://php.net/manual/en/streamwrapper.stream-write.php
     * @param string $data Data to write
221
     * @return integer The number of bytes written
222
     */
223 224
    public function stream_write($data)
    {
225 226 227
        if ( ! $this->stream instanceof WritableStream) {
            return 0;
        }
228

229
        try {
230
            return $this->stream->writeBytes($data);
231 232 233 234
        } catch (Exception $e) {
            trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
            return false;
        }
235
    }
236 237

    /**
238 239
     * Returns a stat template with default values.
     *
240 241
     * @return array
     */
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    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,
        ];
    }
260

261 262 263 264 265 266
    /**
     * Initialize the protocol from the given path.
     *
     * @see StreamWrapper::stream_open()
     * @param string $path
     */
267 268
    private function initProtocol($path)
    {
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
        $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;
308
    }
309
}