MultipartUploader.php 6.1 KB

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