PluginProbe
Media Cloud Sync / 1.1.1
Media Cloud Sync v1.1.1
1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 All 35 releases
media-cloud-sync / includes / sdk / s3 / Aws / Multipart / AbstractUploadManager.php

AbstractUploadManager.php in Media Cloud Sync 1.1.1, at includes/sdk/s3/Aws/Multipart/AbstractUploadManager.php

255 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Dudlewebs\WPMCS\s3\Aws\Multipart;
4
5 use Dudlewebs\WPMCS\s3\Aws\AwsClientInterface as Client;
6 use Dudlewebs\WPMCS\s3\Aws\CommandInterface;
7 use Dudlewebs\WPMCS\s3\Aws\CommandPool;
8 use Dudlewebs\WPMCS\s3\Aws\Exception\AwsException;
9 use Dudlewebs\WPMCS\s3\Aws\Exception\MultipartUploadException;
10 use Dudlewebs\WPMCS\s3\Aws\Result;
11 use Dudlewebs\WPMCS\s3\Aws\ResultInterface;
12 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise;
13 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface;
14 use InvalidArgumentException as IAE;
15 use Dudlewebs\WPMCS\s3\Psr\Http\Message\RequestInterface;
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 /** @var array Default values for base multipart configuration */
25 private static $defaultConfig = ['part_size' => null, 'state' => null, 'concurrency' => self::DEFAULT_CONCURRENCY, 'prepare_data_source' => null, 'before_initiate' => null, 'before_upload' => null, 'before_complete' => null, 'exception_class' => 'Dudlewebs\\WPMCS\\s3\\Aws\\Exception\\MultipartUploadException'];
26 /** @var Client Client used for the upload. */
27 protected $client;
28 /** @var array Configuration used to perform the upload. */
29 protected $config;
30 /** @var array Service-specific information about the upload workflow. */
31 protected $info;
32 /** @var PromiseInterface Promise that represents the multipart upload. */
33 protected $promise;
34 /** @var UploadState State used to manage the upload. */
35 protected $state;
36 /**
37 * @param Client $client
38 * @param array $config
39 */
40 public function __construct(Client $client, array $config = [])
41 {
42 $this->client = $client;
43 $this->info = $this->loadUploadWorkflowInfo();
44 $this->config = $config + self::$defaultConfig;
45 $this->state = $this->determineState();
46 }
47 /**
48 * Returns the current state of the upload
49 *
50 * @return UploadState
51 */
52 public function getState()
53 {
54 return $this->state;
55 }
56 /**
57 * Upload the source using multipart upload operations.
58 *
59 * @return Result The result of the CompleteMultipartUpload operation.
60 * @throws \LogicException if the upload is already complete or aborted.
61 * @throws MultipartUploadException if an upload operation fails.
62 */
63 public function upload()
64 {
65 return $this->promise()->wait();
66 }
67 /**
68 * Upload the source asynchronously using multipart upload operations.
69 *
70 * @return PromiseInterface
71 */
72 public function promise()
73 {
74 if ($this->promise) {
75 return $this->promise;
76 }
77 return $this->promise = Promise\Coroutine::of(function () {
78 // Initiate the upload.
79 if ($this->state->isCompleted()) {
80 throw new \LogicException('This multipart upload has already ' . 'been completed or aborted.');
81 }
82 if (!$this->state->isInitiated()) {
83 // Execute the prepare callback.
84 if (\is_callable($this->config["prepare_data_source"])) {
85 $this->config["prepare_data_source"]();
86 }
87 $result = (yield $this->execCommand('initiate', $this->getInitiateParams()));
88 $this->state->setUploadId($this->info['id']['upload_id'], $result[$this->info['id']['upload_id']]);
89 $this->state->setStatus(UploadState::INITIATED);
90 }
91 // Create a command pool from a generator that yields UploadPart
92 // commands for each upload part.
93 $resultHandler = $this->getResultHandler($errors);
94 $commands = new CommandPool($this->client, $this->getUploadCommands($resultHandler), ['concurrency' => $this->config['concurrency'], 'before' => $this->config['before_upload']]);
95 // Execute the pool of commands concurrently, and process errors.
96 (yield $commands->promise());
97 if ($errors) {
98 throw new $this->config['exception_class']($this->state, $errors);
99 }
100 // Complete the multipart upload.
101 (yield $this->execCommand('complete', $this->getCompleteParams()));
102 $this->state->setStatus(UploadState::COMPLETED);
103 })->otherwise($this->buildFailureCatch());
104 }
105 private function transformException($e)
106 {
107 // Throw errors from the operations as a specific Multipart error.
108 if ($e instanceof AwsException) {
109 $e = new $this->config['exception_class']($this->state, $e);
110 }
111 throw $e;
112 }
113 private function buildFailureCatch()
114 {
115 if (\interface_exists("Throwable")) {
116 return function (\Throwable $e) {
117 return $this->transformException($e);
118 };
119 } else {
120 return function (\Exception $e) {
121 return $this->transformException($e);
122 };
123 }
124 }
125 protected function getConfig()
126 {
127 return $this->config;
128 }
129 /**
130 * Provides service-specific information about the multipart upload
131 * workflow.
132 *
133 * This array of data should include the keys: 'command', 'id', and 'part_num'.
134 *
135 * @return array
136 */
137 protected abstract function loadUploadWorkflowInfo();
138 /**
139 * Determines the part size to use for upload parts.
140 *
141 * Examines the provided partSize value and the source to determine the
142 * best possible part size.
143 *
144 * @throws \InvalidArgumentException if the part size is invalid.
145 *
146 * @return int
147 */
148 protected abstract function determinePartSize();
149 /**
150 * Uses information from the Command and Result to determine which part was
151 * uploaded and mark it as uploaded in the upload's state.
152 *
153 * @param CommandInterface $command
154 * @param ResultInterface $result
155 */
156 protected abstract function handleResult(CommandInterface $command, ResultInterface $result);
157 /**
158 * Gets the service-specific parameters used to initiate the upload.
159 *
160 * @return array
161 */
162 protected abstract function getInitiateParams();
163 /**
164 * Gets the service-specific parameters used to complete the upload.
165 *
166 * @return array
167 */
168 protected abstract function getCompleteParams();
169 /**
170 * Based on the config and service-specific workflow info, creates a
171 * `Promise` for an `UploadState` object.
172 *
173 * @return PromiseInterface A `Promise` that resolves to an `UploadState`.
174 */
175 private function determineState()
176 {
177 // If the state was provided via config, then just use it.
178 if ($this->config['state'] instanceof UploadState) {
179 return $this->config['state'];
180 }
181 // Otherwise, construct a new state from the provided identifiers.
182 $required = $this->info['id'];
183 $id = [$required['upload_id'] => null];
184 unset($required['upload_id']);
185 foreach ($required as $key => $param) {
186 if (!$this->config[$key]) {
187 throw new IAE('You must provide a value for "' . $key . '" in ' . 'your config for the MultipartUploader for ' . $this->client->getApi()->getServiceFullName() . '.');
188 }
189 $id[$param] = $this->config[$key];
190 }
191 $state = new UploadState($id);
192 $state->setPartSize($this->determinePartSize());
193 return $state;
194 }
195 /**
196 * Executes a MUP command with all of the parameters for the operation.
197 *
198 * @param string $operation Name of the operation.
199 * @param array $params Service-specific params for the operation.
200 *
201 * @return PromiseInterface
202 */
203 protected function execCommand($operation, array $params)
204 {
205 // Create the command.
206 $command = $this->client->getCommand($this->info['command'][$operation], $params + $this->state->getId());
207 // Execute the before callback.
208 if (\is_callable($this->config["before_{$operation}"])) {
209 $this->config["before_{$operation}"]($command);
210 }
211 // Execute the command asynchronously and return the promise.
212 return $this->client->executeAsync($command);
213 }
214 /**
215 * Returns a middleware for processing responses of part upload operations.
216 *
217 * - Adds an onFulfilled callback that calls the service-specific
218 * handleResult method on the Result of the operation.
219 * - Adds an onRejected callback that adds the error to an array of errors.
220 * - Has a passedByRef $errors arg that the exceptions get added to. The
221 * caller should use that &$errors array to do error handling.
222 *
223 * @param array $errors Errors from upload operations are added to this.
224 *
225 * @return callable
226 */
227 protected function getResultHandler(&$errors = [])
228 {
229 return function (callable $handler) use(&$errors) {
230 return function (CommandInterface $command, RequestInterface $request = null) use($handler, &$errors) {
231 return $handler($command, $request)->then(function (ResultInterface $result) use($command) {
232 $this->handleResult($command, $result);
233 return $result;
234 }, function (AwsException $e) use(&$errors) {
235 $errors[$e->getCommand()[$this->info['part_num']]] = $e;
236 return new Result();
237 });
238 };
239 };
240 }
241 /**
242 * Creates a generator that yields part data for the upload's source.
243 *
244 * Yields associative arrays of parameters that are ultimately merged in
245 * with others to form the complete parameters of a command. This can
246 * include the Body parameter, which is a limited stream (i.e., a Stream
247 * object, decorated with a LimitStream).
248 *
249 * @param callable $resultHandler
250 *
251 * @return \Generator
252 */
253 protected abstract function getUploadCommands(callable $resultHandler);
254 }
255