PluginProbe
Media Cloud Sync / 1.2.12
Media Cloud Sync v1.2.12
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.2.12, at includes/sdk/s3/Aws/Waiter.php

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