PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.4 3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / Destination / Vendors / S3 / Client / ClientManager.php
backup / src / JetBackup / Destination / Vendors / S3 / Client Last commit date
Exception 1 year ago .htaccess 1 year ago Client.php 1 year ago ClientManager.php 2 months ago ClientRetry.php 1 year ago ListObjects.php 1 year ago ObjectData.php 1 year ago index.html 1 year ago web.config 1 year ago
ClientManager.php
410 lines
1 <?php
2 /*
3 *
4 * JetBackup @ package
5 * Created By Idan Ben-Ezra
6 *
7 * Copyrights @ JetApps
8 * https://www.jetapps.com
9 *
10 **/
11 namespace JetBackup\Destination\Vendors\S3\Client;
12
13 use Exception;
14 use JetBackup\Destination\Vendors\S3\Client\Exception\ClientException;
15 use JetBackup\Exception\IOException;
16 use JetBackup\Log\LogController;
17 use JetBackup\Web\File\FileChunk;
18 use JetBackup\Web\File\FileChunkIterator;
19 use JetBackup\Web\File\FileException;
20 use JetBackup\Web\File\FileStream;
21 use SimpleXMLElement;
22
23 defined( '__JETBACKUP__' ) or die( 'Restricted access' );
24
25 class ClientManager {
26
27 const PUT_CHUNK_SIZE = 1073741824; // 1GB
28 const KEEP_ALIVE_TIMEOUT = 60;
29 const KEEP_ALIVE_REQUESTS = 100;
30 const RETRIES = 0;
31
32 private LogController $_logController;
33 private ?string $_key;
34 private ?string $_secret;
35 private ?string $_region;
36 private ?string $_bucket;
37 private ?string $_endpoint;
38 private ?bool $_verifyssl;
39 private ?int $_retries;
40 private ?bool $_verifyupload;
41 private ?int $_chunk_size;
42 private ?int $_keepalive_timeout;
43 private ?int $_keepalive_requests;
44 private int $_retry_upload;
45 private ?Client $_client = null;
46
47 /**
48 * @param LogController $logController
49 * @param string|null $key
50 * @param string|null $secret
51 * @param string|null $region
52 * @param string|null $bucket
53 * @param string|null $endpoint
54 * @param bool|null $verifyssl
55 * @param int|null $chunk_size
56 * @param int|null $keepalive_timeout
57 * @param int|null $keepalive_requests
58 * @param int|null $retries
59 */
60 public function __construct(LogController $logController, ?string $key=null, ?string $secret=null, ?string $region=null, ?string $bucket=null, ?string $endpoint=null, ?bool $verifyssl=true, ?int $chunk_size=null, ?int $keepalive_timeout=null, ?int $keepalive_requests=null, ?int $retries=null) {
61 $this->setLogController($logController);
62 if($key) $this->setAccessKey($key);
63 if($secret) $this->setSecretKey($secret);
64 if($region) $this->setRegion($region);
65 if($bucket) $this->setBucket($bucket);
66 if($endpoint) $this->setEndpoint($endpoint);
67 if($verifyssl) $this->setVerifySSL($verifyssl);
68 if($chunk_size) $this->setChunkSize($chunk_size);
69 if($keepalive_timeout) $this->setKeepAliveTimeout($keepalive_timeout);
70 if($keepalive_requests) $this->setKeepAliveRequests($keepalive_requests);
71 if($retries) $this->setRetries($retries);
72 $this->_retry_upload = 0;
73 }
74
75 /**
76 * @return Client
77 */
78 public function getClient():Client {
79 if (!$this->_client) $this->_client = new Client($this->getAccessKey(), $this->getSecretKey(), $this->getRegion(), $this->getBucket(), $this->getEndpoint(), $this->getVerifySSL(), $this->getKeepAliveTimeout(), $this->getKeepAliveRequests());
80 return $this->_client;
81 }
82
83 /**
84 * @return LogController
85 */
86 public function getLogController():LogController { return $this->_logController; }
87
88 /**
89 * @param LogController $logController
90 *
91 * @return void
92 */
93 public function setLogController(LogController $logController):void { $this->_logController = $logController; }
94
95 /**
96 * @return string|null
97 */
98 public function getAccessKey():?string { return $this->_key; }
99
100 /**
101 * @param string $key
102 *
103 * @return void
104 */
105 public function setAccessKey(string $key):void { $this->_key = $key; }
106
107 /**
108 * @return string|null
109 */
110 public function getSecretKey():?string { return $this->_secret; }
111
112 /**
113 * @param string $key
114 *
115 * @return void
116 */
117 public function setSecretKey(string $key):void { $this->_secret = $key; }
118
119 /**
120 * @return string|null
121 */
122 public function getRegion():?string { return $this->_region; }
123
124 /**
125 * @param string $region
126 *
127 * @return void
128 */
129 public function setRegion(string $region):void { $this->_region = $region; }
130
131 /**
132 * @return string|null
133 */
134 public function getBucket():?string { return $this->_bucket; }
135
136 /**
137 * @param string $bucket
138 *
139 * @return void
140 */
141 public function setBucket(string $bucket):void { $this->_bucket = $bucket; }
142
143 /**
144 * @return string|null
145 */
146 public function getEndpoint():?string { return $this->_endpoint; }
147
148 /**
149 * @param string $endpoint
150 *
151 * @return void
152 */
153 public function setEndpoint(string $endpoint):void { $this->_endpoint = $endpoint; }
154
155 /**
156 * @return bool
157 */
158 public function getVerifySSL():bool { return !!$this->_verifyssl; }
159
160 /**
161 * @param bool $verifyssl
162 *
163 * @return void
164 */
165 public function setVerifySSL(bool $verifyssl):void { $this->_verifyssl = !!$verifyssl; }
166
167 /**
168 * @return int
169 */
170 public function getChunkSize():int { return $this->_chunk_size ?: self::PUT_CHUNK_SIZE; }
171
172 /**
173 * @param int $size
174 *
175 * @return void
176 */
177 public function setChunkSize(int $size):void { $this->_chunk_size = $size; }
178
179 /**
180 * @return int
181 */
182 public function getKeepAliveTimeout():int { return $this->_keepalive_timeout?: self::KEEP_ALIVE_TIMEOUT; }
183
184 /**
185 * @param int $timeout
186 *
187 * @return void
188 */
189 public function setKeepAliveTimeout(int $timeout):void { $this->_keepalive_timeout = $timeout; }
190
191 /**
192 * @return int
193 */
194 public function getKeepAliveRequests():int { return $this->_keepalive_requests?: self::KEEP_ALIVE_REQUESTS; }
195
196 /**
197 * @param int $queries
198 *
199 * @return void
200 */
201 public function setKeepAliveRequests(int $queries):void { $this->_keepalive_requests = $queries; }
202
203 /**
204 * @return int
205 */
206 public function getRetries():int { return $this->_retries?: self::RETRIES; }
207
208 /**
209 * @param int $retries
210 *
211 * @return void
212 */
213 public function setRetries(int $retries):void { $this->_retries = $retries; }
214
215 /**
216 * @return ClientRetry
217 */
218 public static function client():ClientRetry { return new ClientRetry(); }
219
220 /**
221 * @param string $prefix A string used to group keys. When specified, the response will only contain objects with keys beginning with the string.
222 * @param int $limit The maximum number of objects to return. Defaults to 1,000.
223 * @param string $token The key (object name) to start with when listing objects. For use with pagination (e.g. when then number of objects in the result exceeds the specified max-keys).
224 * @param string $delimiter A single character used to group keys. When specified, the response will only contain keys up to its first occurrence. (E.g. Using a slash as the delimiter can allow you to list keys as if they were folders, especially in combination with a prefix.)
225 *
226 * @return ListObjects
227 * @throws ClientException
228 */
229 public function listObjects(string $prefix='', int $limit=0, string $token='', string $delimiter='/'):ListObjects {
230
231 // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
232 //encoding-type=EncodingType&fetch-owner=FetchOwner&max-keys=MaxKeys&start-after=StartAfter
233
234 $params = [ 'list-type' => 2 ];
235 if($delimiter) $params['delimiter'] = $delimiter;
236 if($token) $params['continuation-token'] = $token;
237 if($limit) $params['max-keys'] = $limit;
238 if($prefix) {
239 if(!str_ends_with($prefix, '/')) $prefix .= '/';
240 $params['prefix'] = $prefix;
241 }
242
243 $listObjects = new ListObjects();
244
245 $result = self::client()->func('get')->args('/', $params)->exec($this);
246 $output = [];
247
248 // NextContinuationToken is sent when isTruncated is true, which means there are more keys in the bucket that can be listed.
249 // The next list requests to Amazon S3 can be continued with this NextContinuationToken.
250 if(isset($result->Body->IsTruncated)) $listObjects->setIsTruncated($result->Body->IsTruncated);
251 if(isset($result->Body->NextContinuationToken)) $listObjects->setNextContinuationToken($result->Body->NextContinuationToken);
252
253 foreach($result->Body->CommonPrefixes as $objectData) {
254 $key = (string) $objectData->Prefix;
255 $object = new ObjectData();
256 $object->setKey($key);
257 $output[] = $object;
258 }
259
260 foreach($result->Body->Contents as $objectData) {
261 if($prefix == (string) $objectData->Key) continue;
262 $object = new ObjectData();
263 $object->setKey($objectData->Key);
264 //$object->setEtag($objectData->ETag);
265 $object->setSize(intval($objectData->Size));
266 //$object->setType($objectData->Type);
267 $object->setMtime($objectData->{"LastModified"} ? strtotime($objectData->{"LastModified"}) : 0);
268 $output[] = $object;
269 }
270
271 $listObjects->setObjectsList($output);
272
273 return $listObjects;
274 }
275
276 /**
277 * @return string|null
278 * @throws ClientException
279 */
280 public function getBucketRegion():?string {
281 $result = self::client()->func('get')->args('', [ 'location' => 1 ])->exec($this);
282 if(!isset($result->Body->{0})) return null;
283 return $result->Body->{0};
284 }
285
286 /**
287 * @param string $key
288 * @param string|null $destination
289 * @param int $start
290 * @param int $end
291 *
292 * @return ObjectData
293 * @throws ClientException
294 */
295 public function getObject(string $key, ?string $destination=null, int $start=0, int $end=0):ObjectData {
296 if($destination) {
297 if($start || $end) $result = self::client()->func('getObjectRange')->args($key, $destination, $start, $end)->exec($this);
298 else $result = self::client()->func('getObject')->args($key, $destination)->exec($this);
299 } else $result = self::client()->func('head')->args($key)->exec($this);
300 $object = new ObjectData();
301 $object->setKey($key);
302 //$object->setEtag($result->Headers->{"etag"});
303 $object->setSize(intval($result->Headers->{"content-length"}));
304 //$object->setType($result->Headers->{"x-rgw-object-type"});
305 $object->setMtime($result->Headers->{"last-modified"} ? strtotime($result->Headers->{"last-modified"}) : 0);
306 return $object;
307 }
308
309 /**
310 * @param string $object
311 *
312 * @return void
313 * @throws ClientException
314 */
315 public function deleteObject(string $object):void {
316 self::client()->func('delete')->args($object)->exec($this);
317 }
318
319 /**
320 * @param string $directory
321 *
322 * @return void
323 * @throws ClientException
324 */
325 public function createObject(string $directory):void {
326 self::client()->func('putString')->args('', $directory)->exec($this);
327 }
328
329 /**
330 * @param string $destination
331 *
332 * @return string
333 * @throws ClientException
334 */
335 public function createUploadID(string $destination):string {
336 $result = self::client()->func('post')->args($destination, ['uploads' => 1])->exec($this);
337 return (string) $result->Body->UploadId;
338 }
339
340 /**
341 * @param string $destination
342 * @param string $upload_id
343 *
344 * @return object|mixed
345 * @throws ClientException
346 */
347 public function listUploadParts(string $destination, string $upload_id, ?int $partNumberMarker=null):object {
348 $params = ['uploadId' => $upload_id];
349 if($partNumberMarker) $params['part-number-marker'] = $partNumberMarker;
350 return self::client()->func('get')->args($destination, $params)->exec($this);
351 }
352
353 /**
354 * @param string $source
355 * @param string $destination
356 *
357 * @return void
358 * @throws ClientException
359 */
360 public function putObject(string $source, string $destination):void {
361 if(!file_exists($source) || !is_file($source)) throw new ClientException("Invalid source provided");
362
363 try {
364 $file = new FileStream($source);
365 } catch(FileException $e) {
366 throw new ClientException($e->getMessage(), $e->getCode(), $e);
367 }
368
369 try {
370 $chunk = new FileChunk($file, $this->getChunkSize());
371 $this->getLogController()->logDebug('[_putObjectSingle]');
372 $this->getLogController()->logDebug('File: ' . $file->getFile());
373 $this->getLogController()->logDebug('ChunkSize: ' . $chunk->getSize());
374 } catch(IOException $e) {
375 throw new ClientException($e->getMessage(), $e->getCode(), $e);
376 }
377
378 self::client()
379 ->func('putChunk')
380 ->args($chunk, $destination)
381 ->retryCallback(function() use ($chunk) {
382 // rewind the chunk on each retry, we need to send the data from the strat
383 $chunk->rewind();
384 })
385 ->exec($this);
386 }
387
388 /**
389 * @throws ClientException
390 */
391 public function putChunk(FileChunk $chunk, string $destination, string $upload_id, int $part_number) {
392 return self::client()->func('putChunk')
393 ->args($chunk, $destination, $upload_id, $part_number)
394 ->exec($this);
395 }
396
397 /**
398 * @throws ClientException
399 */
400 public function closeChunkedUpload(string $destination, string $upload_id, string $document) {
401 self::client()
402 ->func('post')
403 ->args('/' . $destination, [ 'uploadId' => $upload_id ], $document, 'application/xml')
404 ->exec($this);
405 }
406
407 public function __destruct() {
408 if($this->_client) $this->_client->close();
409 }
410 }