| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 | namespace Aws\S3;
 | 
        
           |  |  | 3 |   | 
        
           |  |  | 4 | use Aws\HashingStream;
 | 
        
           |  |  | 5 | use Aws\Multipart\AbstractUploader;
 | 
        
           |  |  | 6 | use Aws\PhpHash;
 | 
        
           |  |  | 7 | use Aws\ResultInterface;
 | 
        
           |  |  | 8 | use GuzzleHttp\Psr7;
 | 
        
           |  |  | 9 | use Psr\Http\Message\StreamInterface as Stream;
 | 
        
           |  |  | 10 | use Aws\S3\Exception\S3MultipartUploadException;
 | 
        
           |  |  | 11 |   | 
        
           |  |  | 12 | /**
 | 
        
           |  |  | 13 |  * Encapsulates the execution of a multipart upload to S3 or Glacier.
 | 
        
           |  |  | 14 |  */
 | 
        
           |  |  | 15 | class MultipartUploader extends AbstractUploader
 | 
        
           |  |  | 16 | {
 | 
        
           |  |  | 17 |     use MultipartUploadingTrait;
 | 
        
           |  |  | 18 |   | 
        
           |  |  | 19 |     const PART_MIN_SIZE = 5242880;
 | 
        
           |  |  | 20 |     const PART_MAX_SIZE = 5368709120;
 | 
        
           |  |  | 21 |     const PART_MAX_NUM = 10000;
 | 
        
           |  |  | 22 |   | 
        
           |  |  | 23 |     /**
 | 
        
           |  |  | 24 |      * Creates a multipart upload for an S3 object.
 | 
        
           |  |  | 25 |      *
 | 
        
           |  |  | 26 |      * The valid configuration options are as follows:
 | 
        
           |  |  | 27 |      *
 | 
        
           |  |  | 28 |      * - acl: (string) ACL to set on the object being upload. Objects are
 | 
        
           |  |  | 29 |      *   private by default.
 | 
        
           |  |  | 30 |      * - before_complete: (callable) Callback to invoke before the
 | 
        
           |  |  | 31 |      *   `CompleteMultipartUpload` operation. The callback should have a
 | 
        
           |  |  | 32 |      *   function signature like `function (Aws\Command $command) {...}`.
 | 
        
           |  |  | 33 |      * - before_initiate: (callable) Callback to invoke before the
 | 
        
           |  |  | 34 |      *   `CreateMultipartUpload` operation. The callback should have a function
 | 
        
           |  |  | 35 |      *   signature like `function (Aws\Command $command) {...}`.
 | 
        
           |  |  | 36 |      * - before_upload: (callable) Callback to invoke before any `UploadPart`
 | 
        
           |  |  | 37 |      *   operations. The callback should have a function signature like
 | 
        
           |  |  | 38 |      *   `function (Aws\Command $command) {...}`.
 | 
        
           |  |  | 39 |      * - bucket: (string, required) Name of the bucket to which the object is
 | 
        
           |  |  | 40 |      *   being uploaded, or an S3 access point ARN.
 | 
        
           |  |  | 41 |      * - concurrency: (int, default=int(5)) Maximum number of concurrent
 | 
        
           |  |  | 42 |      *   `UploadPart` operations allowed during the multipart upload.
 | 
        
           |  |  | 43 |      * - key: (string, required) Key to use for the object being uploaded.
 | 
        
           |  |  | 44 |      * - params: (array) An array of key/value parameters that will be applied
 | 
        
           |  |  | 45 |      *   to each of the sub-commands run by the uploader as a base.
 | 
        
           |  |  | 46 |      *   Auto-calculated options will override these parameters. If you need
 | 
        
           |  |  | 47 |      *   more granularity over parameters to each sub-command, use the before_*
 | 
        
           |  |  | 48 |      *   options detailed above to update the commands directly.
 | 
        
           |  |  | 49 |      * - part_size: (int, default=int(5242880)) Part size, in bytes, to use when
 | 
        
           |  |  | 50 |      *   doing a multipart upload. This must between 5 MB and 5 GB, inclusive.
 | 
        
           |  |  | 51 |      * - prepare_data_source: (callable) Callback to invoke before starting the
 | 
        
           |  |  | 52 |      *   multipart upload workflow. The callback should have a function
 | 
        
           |  |  | 53 |      *   signature like `function () {...}`.
 | 
        
           |  |  | 54 |      * - state: (Aws\Multipart\UploadState) An object that represents the state
 | 
        
           |  |  | 55 |      *   of the multipart upload and that is used to resume a previous upload.
 | 
        
           |  |  | 56 |      *   When this option is provided, the `bucket`, `key`, and `part_size`
 | 
        
           |  |  | 57 |      *   options are ignored.
 | 
        
           | 1441 | ariadna | 58 |      * - track_upload: (boolean) Set true to track status in 1/8th increments
 | 
        
           |  |  | 59 |      *   for upload.
 | 
        
           | 1 | efrain | 60 |      *
 | 
        
           |  |  | 61 |      * @param S3ClientInterface $client Client used for the upload.
 | 
        
           |  |  | 62 |      * @param mixed             $source Source of the data to upload.
 | 
        
           |  |  | 63 |      * @param array             $config Configuration used to perform the upload.
 | 
        
           |  |  | 64 |      */
 | 
        
           |  |  | 65 |     public function __construct(
 | 
        
           |  |  | 66 |         S3ClientInterface $client,
 | 
        
           |  |  | 67 |         $source,
 | 
        
           |  |  | 68 |         array $config = []
 | 
        
           |  |  | 69 |     ) {
 | 
        
           |  |  | 70 |         parent::__construct($client, $source, array_change_key_case($config) + [
 | 
        
           |  |  | 71 |             'bucket' => null,
 | 
        
           |  |  | 72 |             'key'    => null,
 | 
        
           |  |  | 73 |             'exception_class' => S3MultipartUploadException::class,
 | 
        
           |  |  | 74 |         ]);
 | 
        
           | 1441 | ariadna | 75 |   | 
        
           |  |  | 76 |         if ($this->displayProgress) {
 | 
        
           |  |  | 77 |             $this->getState()->setProgressThresholds($this->source->getSize());
 | 
        
           |  |  | 78 |         }
 | 
        
           | 1 | efrain | 79 |     }
 | 
        
           |  |  | 80 |   | 
        
           |  |  | 81 |     protected function loadUploadWorkflowInfo()
 | 
        
           |  |  | 82 |     {
 | 
        
           |  |  | 83 |         return [
 | 
        
           |  |  | 84 |             'command' => [
 | 
        
           |  |  | 85 |                 'initiate' => 'CreateMultipartUpload',
 | 
        
           |  |  | 86 |                 'upload'   => 'UploadPart',
 | 
        
           |  |  | 87 |                 'complete' => 'CompleteMultipartUpload',
 | 
        
           |  |  | 88 |             ],
 | 
        
           |  |  | 89 |             'id' => [
 | 
        
           |  |  | 90 |                 'bucket'    => 'Bucket',
 | 
        
           |  |  | 91 |                 'key'       => 'Key',
 | 
        
           |  |  | 92 |                 'upload_id' => 'UploadId',
 | 
        
           |  |  | 93 |             ],
 | 
        
           |  |  | 94 |             'part_num' => 'PartNumber',
 | 
        
           |  |  | 95 |         ];
 | 
        
           |  |  | 96 |     }
 | 
        
           |  |  | 97 |   | 
        
           |  |  | 98 |     protected function createPart($seekable, $number)
 | 
        
           |  |  | 99 |     {
 | 
        
           |  |  | 100 |         // Initialize the array of part data that will be returned.
 | 
        
           |  |  | 101 |         $data = [];
 | 
        
           |  |  | 102 |   | 
        
           |  |  | 103 |         // Apply custom params to UploadPart data
 | 
        
           |  |  | 104 |         $config = $this->getConfig();
 | 
        
           |  |  | 105 |         $params = isset($config['params']) ? $config['params'] : [];
 | 
        
           |  |  | 106 |         foreach ($params as $k => $v) {
 | 
        
           |  |  | 107 |             $data[$k] = $v;
 | 
        
           |  |  | 108 |         }
 | 
        
           |  |  | 109 |   | 
        
           |  |  | 110 |         $data['PartNumber'] = $number;
 | 
        
           |  |  | 111 |   | 
        
           |  |  | 112 |         // Read from the source to create the body stream.
 | 
        
           |  |  | 113 |         if ($seekable) {
 | 
        
           |  |  | 114 |             // Case 1: Source is seekable, use lazy stream to defer work.
 | 
        
           |  |  | 115 |             $body = $this->limitPartStream(
 | 
        
           |  |  | 116 |                 new Psr7\LazyOpenStream($this->source->getMetadata('uri'), 'r')
 | 
        
           |  |  | 117 |             );
 | 
        
           |  |  | 118 |         } else {
 | 
        
           |  |  | 119 |             // Case 2: Stream is not seekable; must store in temp stream.
 | 
        
           |  |  | 120 |             $source = $this->limitPartStream($this->source);
 | 
        
           |  |  | 121 |             $source = $this->decorateWithHashes($source, $data);
 | 
        
           |  |  | 122 |             $body = Psr7\Utils::streamFor();
 | 
        
           |  |  | 123 |             Psr7\Utils::copyToStream($source, $body);
 | 
        
           |  |  | 124 |         }
 | 
        
           |  |  | 125 |   | 
        
           |  |  | 126 |         $contentLength = $body->getSize();
 | 
        
           |  |  | 127 |   | 
        
           |  |  | 128 |         // Do not create a part if the body size is zero.
 | 
        
           |  |  | 129 |         if ($contentLength === 0) {
 | 
        
           |  |  | 130 |             return false;
 | 
        
           |  |  | 131 |         }
 | 
        
           |  |  | 132 |   | 
        
           |  |  | 133 |         $body->seek(0);
 | 
        
           |  |  | 134 |         $data['Body'] = $body;
 | 
        
           |  |  | 135 |   | 
        
           |  |  | 136 |         if (isset($config['add_content_md5'])
 | 
        
           |  |  | 137 |             && $config['add_content_md5'] === true
 | 
        
           |  |  | 138 |         ) {
 | 
        
           |  |  | 139 |             $data['AddContentMD5'] = true;
 | 
        
           |  |  | 140 |         }
 | 
        
           |  |  | 141 |   | 
        
           |  |  | 142 |         $data['ContentLength'] = $contentLength;
 | 
        
           |  |  | 143 |   | 
        
           |  |  | 144 |         return $data;
 | 
        
           |  |  | 145 |     }
 | 
        
           |  |  | 146 |   | 
        
           |  |  | 147 |     protected function extractETag(ResultInterface $result)
 | 
        
           |  |  | 148 |     {
 | 
        
           |  |  | 149 |         return $result['ETag'];
 | 
        
           |  |  | 150 |     }
 | 
        
           |  |  | 151 |   | 
        
           |  |  | 152 |     protected function getSourceMimeType()
 | 
        
           |  |  | 153 |     {
 | 
        
           |  |  | 154 |         if ($uri = $this->source->getMetadata('uri')) {
 | 
        
           |  |  | 155 |             return Psr7\MimeType::fromFilename($uri)
 | 
        
           |  |  | 156 |                 ?: 'application/octet-stream';
 | 
        
           |  |  | 157 |         }
 | 
        
           |  |  | 158 |     }
 | 
        
           |  |  | 159 |   | 
        
           |  |  | 160 |     protected function getSourceSize()
 | 
        
           |  |  | 161 |     {
 | 
        
           |  |  | 162 |         return $this->source->getSize();
 | 
        
           |  |  | 163 |     }
 | 
        
           |  |  | 164 |   | 
        
           |  |  | 165 |     /**
 | 
        
           |  |  | 166 |      * Decorates a stream with a sha256 linear hashing stream.
 | 
        
           |  |  | 167 |      *
 | 
        
           |  |  | 168 |      * @param Stream $stream Stream to decorate.
 | 
        
           |  |  | 169 |      * @param array  $data   Part data to augment with the hash result.
 | 
        
           |  |  | 170 |      *
 | 
        
           |  |  | 171 |      * @return Stream
 | 
        
           |  |  | 172 |      */
 | 
        
           |  |  | 173 |     private function decorateWithHashes(Stream $stream, array &$data)
 | 
        
           |  |  | 174 |     {
 | 
        
           |  |  | 175 |         // Decorate source with a hashing stream
 | 
        
           |  |  | 176 |         $hash = new PhpHash('sha256');
 | 
        
           |  |  | 177 |         return new HashingStream($stream, $hash, function ($result) use (&$data) {
 | 
        
           |  |  | 178 |             $data['ContentSHA256'] = bin2hex($result);
 | 
        
           |  |  | 179 |         });
 | 
        
           |  |  | 180 |     }
 | 
        
           |  |  | 181 | }
 |