-
Notifications
You must be signed in to change notification settings - Fork 438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement stream wrapper for gs:// #323
Conversation
Add StreamWrapper registration instructions to README. Add ability to register/unregister the StreamWrapper. first stab at a StreamingUploader class Refactor uploader Add bucket->getStreamableUploader() Can pass storage options to the uploader and downloader streams via the stream context move register functions into the Storage namespace, autoloaded by composer add StreamingUploaderTest cannot lazy load because we need to know if the file open succeeded rename StreamingUploader to StreamableUploader to match convention Add test for StreamableUploader to test batching Add documentation for Bucket#getStreamableUploader with example. Adding tests for StreamWrapper with the most common use cases. Fix README with the correct usage
The method names need to be underscored instead of camel case because they are the callback methods needed to implement a stream wrapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work so far!
src/Storage/functions.php
Outdated
$protocol = $protocol ?: 'gs'; | ||
if (!in_array($protocol, stream_get_wrappers())) { | ||
stream_wrapper_register($protocol, 'Google\Cloud\Storage\StreamWrapper') | ||
or die("Failed to register '$protocol://' protocol"); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/functions.php
Outdated
{ | ||
$protocol = $protocol ?: 'gs'; | ||
if (!in_array($protocol, stream_get_wrappers())) { | ||
stream_wrapper_register($protocol, 'Google\Cloud\Storage\StreamWrapper') |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/functions.php
Outdated
@@ -0,0 +1,17 @@ | |||
<?php |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
$this->file = substr($url['path'], 1); | ||
$this->mode = $mode; | ||
|
||
$client = $this->getOption('client') ?: new StorageClient(); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Upload/StreamableUploader.php
Outdated
{ | ||
const DEFAULT_WRITE_CHUNK_SIZE = 262144; | ||
|
||
public function __construct(...$args) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Add a test for overriding the StorageClient in a stream wrapper via the context.
src/Storage/StorageClient.php
Outdated
* 'gs'. | ||
* @throws \RuntimeException | ||
*/ | ||
public static function registerStreamWrapper(string $protocol = null) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StorageClient.php
Outdated
*/ | ||
public static function getDefaultClient() | ||
{ | ||
return self::$defaultClient; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Upload/StreamableUploader.php
Outdated
try { | ||
$response = $this->requestWrapper->send($request, $this->requestOptions); | ||
} catch (GoogleException $ex) { | ||
throw new GoogleException( |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Upload/StreamableUploader.php
Outdated
|
||
try { | ||
$response = $this->requestWrapper->send($request, $this->requestOptions); | ||
} catch (GoogleException $ex) { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Enables handling streams with that StorageClient instance as the default StorageClient.
…treaming upload fails
…am handler callback functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer we not use static methods here - The storage client should be instantiated first, so that authentication can be set normally, i.e.
$storage = new StorageClient([
'projectId' => $projectId,
// any other configuration options
]);
$storage->registerStreamWrapper();
src/Storage/StorageClient.php
Outdated
@@ -217,4 +217,32 @@ public function createBucket($name, array $options = []) | |||
$response = $this->connection->insertBucket($options + ['name' => $name, 'project' => $this->projectId]); | |||
return new Bucket($this->connection, $name, $response); | |||
} | |||
|
|||
/** | |||
* Register a this StorageClient as the handler for stream reading/writing. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
README.md
Outdated
require 'vendor/autoload.php'; | ||
|
||
use Google\Cloud\Storage\StorageClient; | ||
StorageClient::registerStreamWrapper(); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
In hhvm, you cannot get options from a null context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything is coming along nicely :). Great work 👍
src/Storage/StorageClient.php
Outdated
* 'gs'. | ||
* @throws \RuntimeException | ||
*/ | ||
public function registerAsStreamWrapper(string $protocol = null) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StorageClient.php
Outdated
*/ | ||
public function registerAsStreamWrapper(string $protocol = null) | ||
{ | ||
if (StreamWrapper::register($protocol)) { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
$protocol = $protocol ?: 'gs'; | ||
if (!in_array($protocol, stream_get_wrappers())) { | ||
if (!stream_wrapper_register($protocol, StreamWrapper::class)) { | ||
throw new RuntimeException("Failed to register '$protocol://' protocol"); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
* 'gs'. | ||
* @throws \RuntimeException | ||
*/ | ||
public static function register(string $protocol = null) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
); | ||
} elseif ($this->isReadable()) { | ||
try { | ||
$this->stream = $this->bucket->object($this->file)->downloadAsStream($this->getOptions()); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
* Uploader that is a special case of the ResumableUploader where we can write | ||
* the file contents in a streaming manner. | ||
*/ | ||
class StreamableUploader extends ResumableUploader |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Upload/StreamableUploader.php
Outdated
*/ | ||
public function __construct() | ||
{ | ||
call_user_func_array(array($this, 'parent::__construct'), func_get_args()); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Upload/StreamableUploader.php
Outdated
*/ | ||
private function resetBuffer($data = "") | ||
{ | ||
$this->buffer = new BufferStream($this->chunkSize); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Upload/StreamableUploader.php
Outdated
* Write some partial data. If there's enough data to send a chunk, | ||
* then we will send a chunk. Any remaining data is sent on close. | ||
* | ||
* @param mixed $data The data being written. Can be a string or stream. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
// Must be public according to the PHP documentation | ||
public $context; | ||
|
||
// a GuzzleHttp\Psr7\StreamInterface instance |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just a couple non-code things noted. David should still review before merge. He has deeper knowledge of Storage than I do.
src/Storage/StorageClient.php
Outdated
/** | ||
* Registers this StorageClient as the handler for stream reading/writing. | ||
* | ||
* @param string $protocol The name of the protocol to use. Defaults to |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
|
||
private $protocol; | ||
private $bucket; | ||
private $file; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
return true; | ||
} | ||
|
||
private function returnError($message, $flags) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
One other thing... rather than all these ./ruleset.xml <?xml version="1.0"?>
<ruleset name="Google-Cloud-PHP-PSR2">
<rule ref="PSR2" />
<rule ref="Generic.Files.LineLength">
<exclude-pattern>src/*/V[0-9]+</exclude-pattern>
</rule>
<rule ref="PSR1.Methods.CamelCapsMethodName">
<exclude-pattern>src/Storage/StreamWrapper.php</exclude-pattern>
</rule>
<file>src</file>
</ruleset>
|
Ignore the camel case method name rule for StreamWrapper as the required callback method names are snake cased.
@jdpedrie Thanks! @dwsupplee Any chance you can take a look? I'd like to publish our WordPress plugin before Next ;) |
Absolutely! Sorry for the delay. I'll do a final once over today :). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all your hard work on this so far 👊 . I tested the code against quite a few scenarios lats night and it held up very well.
Outside of the comments, LGTM!
src/Storage/Bucket.php
Outdated
* Example: | ||
* ``` | ||
* $uploader = $bucket->getStreamableUploader( | ||
* "initial contents", |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/Bucket.php
Outdated
* ['name' => 'data.txt'] | ||
* ); | ||
* | ||
* $uploader->write('some line'); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/Bucket.php
Outdated
* applied using md5 hashing functionality. If true and the | ||
* calculated hash does not match that of the upstream server the | ||
* upload will be rejected. | ||
* @type int $chunkSize If provided the upload will be done in chunks. |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/Bucket.php
Outdated
* for best performance it is recommended to pass in a cached | ||
* version of the already calculated SHA. | ||
* } | ||
* @return StreamableUploader |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/Bucket.php
Outdated
* @param string $file Optional file to try to write. | ||
* @return boolean | ||
*/ | ||
public function isWritable($file = null) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/WriteStream.php
Outdated
* @param string|StreamInterface $data Data to write | ||
* @return int The number of bytes written | ||
*/ | ||
public function write($data) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Upload/StreamableUploader.php
Outdated
/** | ||
* Triggers the upload process. | ||
* | ||
* @param bool $remainder If true, send the all the remaining data and close |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
||
/** | ||
* @group storage | ||
* @group streamWrapper |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
*/ | ||
public function dir_rewinddir() | ||
{ | ||
$this->directoryGenerator = $this->bucket->objects([ |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
src/Storage/StreamWrapper.php
Outdated
/** | ||
* Callback handler for reading an entry from a directory handle. | ||
* | ||
* @return string |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
The mode int is extrapolated into one of publicRead, projectPrivate, or private. We will create a bucket if no path is given (i.e. gs://bucket-name-only/') or if the STREAM_MKDIR_RECURSIVE flag is provided.
Any other ServiceException will be re-thrown. Also added some unit tests to account for this behavior.
The travis runs appear to be borked and I don't have access to kick the jobs. |
I cancelled the queued builds except for the latest. That should help speed things up, but we may be at the mercy of other builds running in the org. |
@chingor13 is there anything else you would like to touch on before we merge? |
@dwsupplee I think it's good to go :) |
Awesome! Thanks so much for this. |
Great work guys! |
This implementation creates 2 new classes:
Note: this will be squashed when it's ready.