3 * Copyright 2007 Google Inc.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 * A user space stream wrapper for reading and writing to Google Cloud Storage.
20 * See: http://www.php.net/manual/en/class.streamwrapper.php
24 namespace google\appengine\ext\cloud_storage_streams
;
26 require_once 'google/appengine/api/cloud_storage/CloudStorageTools.php';
27 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageClient.php';
28 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageDeleteClient.php';
29 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageDirectoryClient.php';
30 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageReadClient.php';
31 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageRenameClient.php';
32 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageUrlStatClient.php';
33 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageWriteClient.php';
34 require_once 'google/appengine/util/array_util.php';
36 use google\appengine\api\cloud_storage\CloudStorageTools
;
37 use google\appengine\util
as util
;
40 * Allowed stream_context options.
41 * "anonymous": Boolean, if set then OAuth tokens will not be generated.
42 * "acl": The ACL to apply when creating an object.
43 * "Content-Type": The content type of the object being written.
45 final class CloudStorageStreamWrapper
{
47 // The client instance that we're using to communicate with GS.
50 // Must be public according to PHP documents - We capture the contents when
51 // constructing objects.
54 const STREAM_OPEN_FOR_INCLUDE
= 0x80;
56 private static $valid_read_modes = ['r', 'rb', 'rt'];
57 private static $valid_write_modes = ['w', 'wb', 'wt'];
60 * Constructs a new stream wrapper.
62 public function __construct() {
66 * Destructs an existing stream wrapper.
68 public function __destruct() {
72 * Close an open directory handle.
74 public function dir_closedir() {
75 assert(isset($this->client
));
76 $this->client
->close();
81 * Open a directory handle.
83 public function dir_opendir($path, $options) {
84 if (!$this->getBucketAndObjectFromPath($path, $bucket, $path)) {
85 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $path),
90 $this->client
= new CloudStorageDirectoryClient($bucket,
93 return $this->client
->initialise();
97 * Read entry from the directory handle.
99 * @return string representing the next filename, of false if there is no
102 public function dir_readdir() {
103 assert(isset($this->client
));
104 return $this->client
->dir_readdir();
108 * Reset the output returned from dir_readdir.
110 * @return bool true if the stream can be rewound, false otherwise.
112 public function dir_rewinddir() {
113 assert(isset($this->client
));
114 return $this->client
->dir_rewinddir();
117 public function mkdir($path, $mode, $options) {
118 if (!$this->getBucketAndObjectFromPath($path, $bucket, $object) ||
120 if (($options | STREAM_REPORT_ERRORS
) != 0) {
121 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $path),
126 $client = new CloudStorageDirectoryClient($bucket,
129 return $client->mkdir($options);
132 public function rmdir($path, $options) {
133 if (!$this->getBucketAndObjectFromPath($path, $bucket, $object) ||
135 if (($options | STREAM_REPORT_ERRORS
) != 0) {
136 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $path),
141 $client = new CloudStorageDirectoryClient($bucket,
144 return $client->rmdir($options);
148 * Rename a cloud storage object.
150 * @return TRUE if the object was renamed, FALSE otherwise
152 public function rename($from, $to) {
153 if (!$this->getBucketAndObjectFromPath($from, $from_bucket, $from_object) ||
154 !isset($from_object)) {
155 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $from),
159 if (!$this->getBucketAndObjectFromPath($to, $to_bucket, $to_object) ||
160 !isset($to_object)) {
161 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $to),
165 $client = new CloudStorageRenameClient($from_bucket,
170 return $client->rename();
174 * Retrieve the underlaying resource of the stream, called in response to
177 * As GS streams have no underlying resource, we can only return false
179 public function stream_cast() {
184 * All resources that were locked, or allocated, by the wrapper should be
187 * No value is returned.
189 public function stream_close() {
190 assert(isset($this->client
));
191 $this->client
->close();
192 $this->client
= null;
196 * Tests for end-of-file on a file pointer.
198 * @return TRUE if the read/write position is at the end of the stream and if
199 * no more data is available to be read, or FALSE otherwise
201 public function stream_eof() {
202 assert(isset($this->client
));
203 return $this->client
->eof();
207 * Flushes the output.
209 * @return TRUE if the cached data was successfully stored (or if there was
210 * no data to store), or FALSE if the data could not be stored.
212 public function stream_flush() {
213 assert(isset($this->client
));
214 return $this->client
->flush();
217 public function stream_metadata($path, $option, $value) {
221 public function stream_open($path, $mode, $options, &$opened_path) {
222 if (!$this->getBucketAndObjectFromPath($path, $bucket, $object) ||
224 if (($options & STREAM_REPORT_ERRORS
) != 0) {
225 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $path),
231 if (($options & self
::STREAM_OPEN_FOR_INCLUDE
) != 0) {
232 $allowed_buckets = explode(",", GAE_INCLUDE_GS_BUCKETS
);
233 $include_allowed = false;
234 foreach ($allowed_buckets as $bucket_name) {
235 $bucket_name = trim($bucket_name);
236 if ($bucket_name === $bucket) {
237 $include_allowed = true;
241 if (!$include_allowed) {
242 if (($options & STREAM_REPORT_ERRORS
) != 0) {
244 sprintf("Not allowed to include/require from bucket '%s'",
252 if (in_array($mode, self
::$valid_read_modes)) {
253 $this->client
= new CloudStorageReadClient($bucket,
256 } else if (in_array($mode, self
::$valid_write_modes)) {
257 $this->client
= new CloudStorageWriteClient($bucket,
261 if (($options & STREAM_REPORT_ERRORS
) != 0) {
262 trigger_error(sprintf("Invalid mode: %s", $mode), E_USER_ERROR
);
267 return $this->client
->initialize();
271 * Read from a stream, return string of bytes.
273 public function stream_read($count) {
274 assert(isset($this->client
));
275 return $this->client
->read($count);
278 public function stream_seek($offset, $whence) {
279 assert(isset($this->client
));
280 return $this->client
->seek($offset, $whence);
283 public function stream_set_option($option, $arg1, $arg2) {
284 assert(isset($this->client
));
288 public function stream_stat() {
289 assert(isset($this->client
));
290 return $this->client
->stat();
293 public function stream_tell() {
294 assert(isset($this->client
));
295 return $this->client
->tell();
299 * Return the number of bytes written.
301 public function stream_write($data) {
302 assert(isset($this->client
));
303 return $this->client
->write($data);
307 * Deletes a file. Called in response to unlink($filename).
309 public function unlink($path) {
310 if (!$this->getBucketAndObjectFromPath($path, $bucket, $object) ||
312 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $path),
317 $this->client
= new CloudStorageDeleteClient($bucket,
320 return $this->client
->delete();
323 public function url_stat($path, $flags) {
324 if (!$this->getBucketAndObjectFromPath($path, $bucket, $object)) {
325 if (($flags & STREAM_URL_STAT_QUIET
) != 0) {
326 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $path),
332 $client = new CloudStorageUrlStatClient($bucket,
336 return $client->stat();
340 * Parse the supplied path and extract the bucket and object names from it.
341 * It is possible that there is no object name in the path and a null will be
342 * returned in the $object parameters if this is the case.
344 private function getBucketAndObjectFromPath($path, &$bucket, &$object) {
345 // Decompose the $path into the GCS url components and check
346 $url_parts = parse_url($path);
348 if ($url_parts === false) {
351 if ($url_parts['scheme'] !== 'gs' ||
empty($url_parts['host'])) {
352 trigger_error(sprintf("Invalid Google Cloud Storage path: %s", $path),
356 $bucket = $url_parts['host'];
358 $path = util\findByKeyOrNull
($url_parts, 'path');
359 if (isset($path) && $path !== "/") {
363 // Validate bucket & object names.
365 CloudStorageTools
::getFilename($bucket, $object);
366 } catch (\InvalidArgumentException
$e) {