PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
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 1.3.0 All 34 releases
media-cloud-sync / includes / sdk / s3 / Aws / Waiter.php

Waiter.php in Media Cloud Sync 1.3.11, at includes/sdk/s3/Aws/Waiter.php

231 lines 8.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;
4
5 use Dudlewebs\WPMCS\s3\Aws\Exception\AwsException;
6 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\Coroutine;
7 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface;
8 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromisorInterface;
9 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\RejectedPromise;
10 /**
11 * "Waiters" are associated with an AWS resource (e.g., EC2 instance), and poll
12 * that resource and until it is in a particular state.
13 * The Waiter object produces a promise that is either a.) resolved once the
14 * waiting conditions are met, or b.) rejected if the waiting conditions cannot
15 * be met or has exceeded the number of allowed attempts at meeting the
16 * conditions. You can use waiters in a blocking or non-blocking way, depending
17 * on whether you call wait() on the promise.
18 * The configuration for the waiter must include information about the operation
19 * and the conditions for wait completion.
20 */
21 class Waiter implements PromisorInterface
22 {
23 /** @var AwsClientInterface Client used to execute each attempt. */
24 private $client;
25 /** @var string Name of the waiter. */
26 private $name;
27 /** @var array Params to use with each attempt operation. */
28 private $args;
29 /** @var array Waiter configuration. */
30 private $config;
31 /** @var array Default configuration options. */
32 private static $defaults = ['initDelay' => 0, 'before' => null];
33 /** @var array Required configuration options. */
34 private static $required = ['acceptors', 'delay', 'maxAttempts', 'operation'];
35 /**
36 * The array of configuration options include:
37 *
38 * - acceptors: (array) Array of acceptor options
39 * - delay: (int) Number of seconds to delay between attempts
40 * - maxAttempts: (int) Maximum number of attempts before failing
41 * - operation: (string) Name of the API operation to use for polling
42 * - before: (callable) Invoked before attempts. Accepts command and tries.
43 *
44 * @param AwsClientInterface $client Client used to execute commands.
45 * @param string $name Waiter name.
46 * @param array $args Command arguments.
47 * @param array $config Waiter config that overrides defaults.
48 *
49 * @throws \InvalidArgumentException if the configuration is incomplete.
50 */
51 public function __construct(AwsClientInterface $client, $name, array $args = [], array $config = [])
52 {
53 $this->client = $client;
54 $this->name = $name;
55 $this->args = $args;
56 // Prepare and validate config.
57 $this->config = $config + self::$defaults;
58 foreach (self::$required as $key) {
59 if (!isset($this->config[$key])) {
60 throw new \InvalidArgumentException('The provided waiter configuration was incomplete.');
61 }
62 }
63 if ($this->config['before'] && !\is_callable($this->config['before'])) {
64 throw new \InvalidArgumentException('The provided "before" callback is not callable.');
65 }
66 MetricsBuilder::appendMetricsCaptureMiddleware($this->client->getHandlerList(), MetricsBuilder::WAITER);
67 }
68 /**
69 * @return Coroutine
70 */
71 public function promise() : PromiseInterface
72 {
73 return Coroutine::of(function () {
74 $name = $this->config['operation'];
75 for ($state = 'retry', $attempt = 1; $state === 'retry'; $attempt++) {
76 // Execute the operation.
77 $args = $this->getArgsForAttempt($attempt);
78 $command = $this->client->getCommand($name, $args);
79 try {
80 if ($this->config['before']) {
81 $this->config['before']($command, $attempt);
82 }
83 $result = (yield $this->client->executeAsync($command));
84 } catch (AwsException $e) {
85 $result = $e;
86 }
87 // Determine the waiter's state and what to do next.
88 $state = $this->determineState($result);
89 if ($state === 'success') {
90 (yield $command);
91 } elseif ($state === 'failed') {
92 $msg = "The {$this->name} waiter entered a failure state.";
93 if ($result instanceof \Exception) {
94 $msg .= ' Reason: ' . $result->getMessage();
95 }
96 (yield new RejectedPromise(new \RuntimeException($msg)));
97 } elseif ($state === 'retry' && $attempt >= $this->config['maxAttempts']) {
98 $state = 'failed';
99 (yield new RejectedPromise(new \RuntimeException("The {$this->name} waiter failed after attempt #{$attempt}.")));
100 }
101 }
102 });
103 }
104 /**
105 * Gets the operation arguments for the attempt, including the delay.
106 *
107 * @param $attempt Number of the current attempt.
108 *
109 * @return mixed integer
110 */
111 private function getArgsForAttempt($attempt)
112 {
113 $args = $this->args;
114 // Determine the delay.
115 $delay = $attempt === 1 ? $this->config['initDelay'] : $this->config['delay'];
116 if (\is_callable($delay)) {
117 $delay = $delay($attempt);
118 }
119 // Set the delay. (Note: handlers except delay in milliseconds.)
120 if (!isset($args['@http'])) {
121 $args['@http'] = [];
122 }
123 $args['@http']['delay'] = $delay * 1000;
124 return $args;
125 }
126 /**
127 * Determines the state of the waiter attempt, based on the result of
128 * polling the resource. A waiter can have the state of "success", "failed",
129 * or "retry".
130 *
131 * @param mixed $result
132 *
133 * @return string Will be "success", "failed", or "retry"
134 */
135 private function determineState($result)
136 {
137 foreach ($this->config['acceptors'] as $acceptor) {
138 $matcher = 'matches' . \ucfirst($acceptor['matcher']);
139 if ($this->{$matcher}($result, $acceptor)) {
140 return $acceptor['state'];
141 }
142 }
143 return $result instanceof \Exception ? 'failed' : 'retry';
144 }
145 /**
146 * @param Result $result Result or exception.
147 * @param array $acceptor Acceptor configuration being checked.
148 *
149 * @return bool
150 */
151 private function matchesPath($result, array $acceptor)
152 {
153 return $result instanceof ResultInterface && $acceptor['expected'] === $result->search($acceptor['argument']);
154 }
155 /**
156 * @param Result $result Result or exception.
157 * @param array $acceptor Acceptor configuration being checked.
158 *
159 * @return bool
160 */
161 private function matchesPathAll($result, array $acceptor)
162 {
163 if (!$result instanceof ResultInterface) {
164 return \false;
165 }
166 $actuals = $result->search($acceptor['argument']) ?: [];
167 // If is empty or not evaluates to an array it must return false.
168 if (empty($actuals) || !\is_array($actuals)) {
169 return \false;
170 }
171 foreach ($actuals as $actual) {
172 if ($actual != $acceptor['expected']) {
173 return \false;
174 }
175 }
176 return \true;
177 }
178 /**
179 * @param Result $result Result or exception.
180 * @param array $acceptor Acceptor configuration being checked.
181 *
182 * @return bool
183 */
184 private function matchesPathAny($result, array $acceptor)
185 {
186 if (!$result instanceof ResultInterface) {
187 return \false;
188 }
189 $actuals = $result->search($acceptor['argument']) ?: [];
190 // If is empty or not evaluates to an array it must return false.
191 if (empty($actuals) || !\is_array($actuals)) {
192 return \false;
193 }
194 return \in_array($acceptor['expected'], $actuals);
195 }
196 /**
197 * @param Result $result Result or exception.
198 * @param array $acceptor Acceptor configuration being checked.
199 *
200 * @return bool
201 */
202 private function matchesStatus($result, array $acceptor)
203 {
204 if ($result instanceof ResultInterface) {
205 return $acceptor['expected'] == $result['@metadata']['statusCode'];
206 }
207 if ($result instanceof AwsException && ($response = $result->getResponse())) {
208 return $acceptor['expected'] == $response->getStatusCode();
209 }
210 return \false;
211 }
212 /**
213 * @param Result $result Result or exception.
214 * @param array $acceptor Acceptor configuration being checked.
215 *
216 * @return bool
217 */
218 private function matchesError($result, array $acceptor)
219 {
220 // If expected is true then the $result should be an instance of
221 // AwsException, otherwise it should not.
222 if (isset($acceptor['expected']) && \is_bool($acceptor['expected'])) {
223 return $acceptor['expected'] === $result instanceof AwsException;
224 }
225 if ($result instanceof AwsException) {
226 return $result->isConnectionError() || $result->getAwsErrorCode() == $acceptor['expected'];
227 }
228 return \false;
229 }
230 }
231