PluginProbe ʕ •ᴥ•ʔ
Transferito: WP Migration / trunk
Transferito: WP Migration vtrunk
trunk 11.4.0 12.0.0 13.1.0 14.0.0 14.0.11 14.0.7 14.1.0 14.1.1 14.1.2 14.1.3 14.1.4
transferito / vendor / aws / aws-sdk-php / src / Multipart / AbstractUploadManager.php
transferito / vendor / aws / aws-sdk-php / src / Multipart Last commit date
AbstractUploadManager.php 11 months ago AbstractUploader.php 11 months ago UploadState.php 11 months ago
AbstractUploadManager.php
320 lines
1 <?php
2 namespace Aws\Multipart;
3
4 use Aws\AwsClientInterface as Client;
5 use Aws\CommandInterface;
6 use Aws\CommandPool;
7 use Aws\Exception\AwsException;
8 use Aws\Exception\MultipartUploadException;
9 use Aws\Result;
10 use Aws\ResultInterface;
11 use GuzzleHttp\Promise;
12 use GuzzleHttp\Promise\PromiseInterface;
13 use InvalidArgumentException as IAE;
14 use Psr\Http\Message\RequestInterface;
15
16 /**
17 * Encapsulates the execution of a multipart upload to S3 or Glacier.
18 *
19 * @internal
20 */
21 abstract class AbstractUploadManager implements Promise\PromisorInterface
22 {
23 const DEFAULT_CONCURRENCY = 5;
24
25 /** @var array Default values for base multipart configuration */
26 private static $defaultConfig = [
27 'part_size' => null,
28 'state' => null,
29 'concurrency' => self::DEFAULT_CONCURRENCY,
30 'prepare_data_source' => null,
31 'before_initiate' => null,
32 'before_upload' => null,
33 'before_complete' => null,
34 'exception_class' => MultipartUploadException::class,
35 ];
36
37 /** @var Client Client used for the upload. */
38 protected $client;
39
40 /** @var array Configuration used to perform the upload. */
41 protected $config;
42
43 /** @var array Service-specific information about the upload workflow. */
44 protected $info;
45
46 /** @var PromiseInterface Promise that represents the multipart upload. */
47 protected $promise;
48
49 /** @var UploadState State used to manage the upload. */
50 protected $state;
51
52 /**
53 * @param Client $client
54 * @param array $config
55 */
56 public function __construct(Client $client, array $config = [])
57 {
58 $this->client = $client;
59 $this->info = $this->loadUploadWorkflowInfo();
60 $this->config = $config + self::$defaultConfig;
61 $this->state = $this->determineState();
62 }
63
64 /**
65 * Returns the current state of the upload
66 *
67 * @return UploadState
68 */
69 public function getState()
70 {
71 return $this->state;
72 }
73
74 /**
75 * Upload the source using multipart upload operations.
76 *
77 * @return Result The result of the CompleteMultipartUpload operation.
78 * @throws \LogicException if the upload is already complete or aborted.
79 * @throws MultipartUploadException if an upload operation fails.
80 */
81 public function upload()
82 {
83 return $this->promise()->wait();
84 }
85
86 /**
87 * Upload the source asynchronously using multipart upload operations.
88 *
89 * @return PromiseInterface
90 */
91 public function promise(): PromiseInterface
92 {
93 if ($this->promise) {
94 return $this->promise;
95 }
96
97 return $this->promise = Promise\Coroutine::of(function () {
98 // Initiate the upload.
99 if ($this->state->isCompleted()) {
100 throw new \LogicException('This multipart upload has already '
101 . 'been completed or aborted.'
102 );
103 }
104
105 if (!$this->state->isInitiated()) {
106 // Execute the prepare callback.
107 if (is_callable($this->config["prepare_data_source"])) {
108 $this->config["prepare_data_source"]();
109 }
110
111 $result = (yield $this->execCommand('initiate', $this->getInitiateParams()));
112 $this->state->setUploadId(
113 $this->info['id']['upload_id'],
114 $result[$this->info['id']['upload_id']]
115 );
116 $this->state->setStatus(UploadState::INITIATED);
117 }
118
119 // Create a command pool from a generator that yields UploadPart
120 // commands for each upload part.
121 $resultHandler = $this->getResultHandler($errors);
122 $commands = new CommandPool(
123 $this->client,
124 $this->getUploadCommands($resultHandler),
125 [
126 'concurrency' => $this->config['concurrency'],
127 'before' => $this->config['before_upload'],
128 ]
129 );
130
131 // Execute the pool of commands concurrently, and process errors.
132 yield $commands->promise();
133 if ($errors) {
134 throw new $this->config['exception_class']($this->state, $errors);
135 }
136
137 // Complete the multipart upload.
138 yield $this->execCommand('complete', $this->getCompleteParams());
139 $this->state->setStatus(UploadState::COMPLETED);
140 })->otherwise($this->buildFailureCatch());
141 }
142
143 private function transformException($e)
144 {
145 // Throw errors from the operations as a specific Multipart error.
146 if ($e instanceof AwsException) {
147 $e = new $this->config['exception_class']($this->state, $e);
148 }
149 throw $e;
150 }
151
152 private function buildFailureCatch()
153 {
154 if (interface_exists("Throwable")) {
155 return function (\Throwable $e) {
156 return $this->transformException($e);
157 };
158 } else {
159 return function (\Exception $e) {
160 return $this->transformException($e);
161 };
162 }
163 }
164
165 protected function getConfig()
166 {
167 return $this->config;
168 }
169
170 /**
171 * Provides service-specific information about the multipart upload
172 * workflow.
173 *
174 * This array of data should include the keys: 'command', 'id', and 'part_num'.
175 *
176 * @return array
177 */
178 abstract protected function loadUploadWorkflowInfo();
179
180 /**
181 * Determines the part size to use for upload parts.
182 *
183 * Examines the provided partSize value and the source to determine the
184 * best possible part size.
185 *
186 * @throws \InvalidArgumentException if the part size is invalid.
187 *
188 * @return int
189 */
190 abstract protected function determinePartSize();
191
192 /**
193 * Uses information from the Command and Result to determine which part was
194 * uploaded and mark it as uploaded in the upload's state.
195 *
196 * @param CommandInterface $command
197 * @param ResultInterface $result
198 */
199 abstract protected function handleResult(
200 CommandInterface $command,
201 ResultInterface $result
202 );
203
204 /**
205 * Gets the service-specific parameters used to initiate the upload.
206 *
207 * @return array
208 */
209 abstract protected function getInitiateParams();
210
211 /**
212 * Gets the service-specific parameters used to complete the upload.
213 *
214 * @return array
215 */
216 abstract protected function getCompleteParams();
217
218 /**
219 * Based on the config and service-specific workflow info, creates a
220 * `Promise` for an `UploadState` object.
221 */
222 private function determineState(): UploadState
223 {
224 // If the state was provided via config, then just use it.
225 if ($this->config['state'] instanceof UploadState) {
226 return $this->config['state'];
227 }
228
229 // Otherwise, construct a new state from the provided identifiers.
230 $required = $this->info['id'];
231 $id = [$required['upload_id'] => null];
232 unset($required['upload_id']);
233 foreach ($required as $key => $param) {
234 if (!$this->config[$key]) {
235 throw new IAE('You must provide a value for "' . $key . '" in '
236 . 'your config for the MultipartUploader for '
237 . $this->client->getApi()->getServiceFullName() . '.');
238 }
239 $id[$param] = $this->config[$key];
240 }
241 $state = new UploadState($id);
242 $state->setPartSize($this->determinePartSize());
243
244 return $state;
245 }
246
247 /**
248 * Executes a MUP command with all of the parameters for the operation.
249 *
250 * @param string $operation Name of the operation.
251 * @param array $params Service-specific params for the operation.
252 *
253 * @return PromiseInterface
254 */
255 protected function execCommand($operation, array $params)
256 {
257 // Create the command.
258 $command = $this->client->getCommand(
259 $this->info['command'][$operation],
260 $params + $this->state->getId()
261 );
262
263 // Execute the before callback.
264 if (is_callable($this->config["before_{$operation}"])) {
265 $this->config["before_{$operation}"]($command);
266 }
267
268 // Execute the command asynchronously and return the promise.
269 return $this->client->executeAsync($command);
270 }
271
272 /**
273 * Returns a middleware for processing responses of part upload operations.
274 *
275 * - Adds an onFulfilled callback that calls the service-specific
276 * handleResult method on the Result of the operation.
277 * - Adds an onRejected callback that adds the error to an array of errors.
278 * - Has a passedByRef $errors arg that the exceptions get added to. The
279 * caller should use that &$errors array to do error handling.
280 *
281 * @param array $errors Errors from upload operations are added to this.
282 *
283 * @return callable
284 */
285 protected function getResultHandler(&$errors = [])
286 {
287 return function (callable $handler) use (&$errors) {
288 return function (
289 CommandInterface $command,
290 RequestInterface $request = null
291 ) use ($handler, &$errors) {
292 return $handler($command, $request)->then(
293 function (ResultInterface $result) use ($command) {
294 $this->handleResult($command, $result);
295 return $result;
296 },
297 function (AwsException $e) use (&$errors) {
298 $errors[$e->getCommand()[$this->info['part_num']]] = $e;
299 return new Result();
300 }
301 );
302 };
303 };
304 }
305
306 /**
307 * Creates a generator that yields part data for the upload's source.
308 *
309 * Yields associative arrays of parameters that are ultimately merged in
310 * with others to form the complete parameters of a command. This can
311 * include the Body parameter, which is a limited stream (i.e., a Stream
312 * object, decorated with a LimitStream).
313 *
314 * @param callable $resultHandler
315 *
316 * @return \Generator
317 */
318 abstract protected function getUploadCommands(callable $resultHandler);
319 }
320