Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws\Glacier;
3
 
4
use Aws\CommandInterface;
5
use Aws\HashingStream;
6
use Aws\Multipart\AbstractUploader;
7
use Aws\Multipart\UploadState;
8
use Aws\PhpHash;
9
use Aws\ResultInterface;
10
use GuzzleHttp\Psr7;
11
use Psr\Http\Message\StreamInterface as Stream;
12
 
13
/**
14
 * Encapsulates the execution of a multipart upload to Glacier.
15
 */
16
class MultipartUploader extends AbstractUploader
17
{
18
    const PART_MIN_SIZE = 1048576;
19
 
20
    private static $validPartSizes = [
21
        1048576,    //   1 MB
22
        2097152,    //   2 MB
23
        4194304,    //   4 MB
24
        8388608,    //   8 MB
25
        16777216,   //  16 MB
26
        33554432,   //  32 MB
27
        67108864,   //  64 MB
28
        134217728,  // 128 MB
29
        268435456,  // 256 MB
30
        536870912,  // 512 MB
31
        1073741824, //   1 GB
32
        2147483648, //   2 GB
33
        4294967296, //   4 GB
34
    ];
35
 
36
    /**
37
     * Creates an UploadState object for a multipart upload by querying the
38
     * service for the specified upload's information.
39
     *
40
     * @param GlacierClient $client    GlacierClient object to use.
41
     * @param string        $vaultName Vault name for the multipart upload.
42
     * @param string        $uploadId  Upload ID for the multipart upload.
43
     * @param string        $accountId Account ID for the multipart upload.
44
     *
45
     * @return UploadState
46
     */
47
    public static function getStateFromService(
48
        GlacierClient $client,
49
        $vaultName,
50
        $uploadId,
51
        $accountId = '-'
52
    ) {
53
        $state = new UploadState([
54
            'accountId' => $accountId,
55
            'vaultName' => $vaultName,
56
            'uploadId'  => $uploadId,
57
        ]);
58
 
59
        foreach ($client->getPaginator('ListParts', $state->getId()) as $result) {
60
            // Get the part size from the first part in the first result.
61
            if (!$state->getPartSize()) {
62
                $state->setPartSize($result['PartSizeInBytes']);
63
            }
64
            // Mark all the parts returned by ListParts as uploaded.
65
            foreach ($result['Parts'] as $part) {
66
                list($rangeIndex, $rangeSize) = self::parseRange(
67
                    $part['RangeInBytes'],
68
                    $state->getPartSize()
69
                );
70
                $state->markPartAsUploaded($rangeIndex, [
71
                    'size'     => $rangeSize,
72
                    'checksum' => $part['SHA256TreeHash'],
73
                ]);
74
            }
75
        }
76
 
77
        $state->setStatus(UploadState::INITIATED);
78
 
79
        return $state;
80
    }
81
 
82
    /**
83
     * Creates a multipart upload for a Glacier archive.
84
     *
85
     * The valid configuration options are as follows:
86
     *
87
     * - account_id: (string, default=string('-')) Account ID for the archive
88
     *   being uploaded, if different from the account making the request.
89
     * - archive_description: (string) Description of the archive.
90
     * - before_complete: (callable) Callback to invoke before the
91
     *   `CompleteMultipartUpload` operation. The callback should have a
92
     *   function signature like `function (Aws\Command $command) {...}`.
93
     * - before_initiate: (callable) Callback to invoke before the
94
     *   `InitiateMultipartUpload` operation. The callback should have a
95
     *   function signature like `function (Aws\Command $command) {...}`.
96
     * - before_upload: (callable) Callback to invoke before any
97
     *   `UploadMultipartPart` operations. The callback should have a function
98
     *   signature like `function (Aws\Command $command) {...}`.
99
     * - concurrency: (int, default=int(3)) Maximum number of concurrent
100
     *   `UploadMultipartPart` operations allowed during the multipart upload.
101
     * - part_size: (int, default=int(1048576)) Part size, in bytes, to use when
102
     *   doing a multipart upload. This must between 1 MB and 4 GB, and must be
103
     *   a power of 2 (in megabytes).
104
     * - prepare_data_source: (callable) Callback to invoke before starting the
105
     *   multipart upload workflow. The callback should have a function
106
     *   signature like `function () {...}`.
107
     * - state: (Aws\Multipart\UploadState) An object that represents the state
108
     *   of the multipart upload and that is used to resume a previous upload.
109
     *   When this options is provided, the `account_id`, `key`, and `part_size`
110
     *   options are ignored.
111
     * - vault_name: (string, required) Vault name to use for the archive being
112
     *   uploaded.
113
     *
114
     * @param GlacierClient $client Client used for the upload.
115
     * @param mixed         $source Source of the data to upload.
116
     * @param array         $config Configuration used to perform the upload.
117
     */
118
    public function __construct(GlacierClient $client, $source, array $config = [])
119
    {
120
        parent::__construct($client, $source, $config + [
121
            'account_id' => '-',
122
            'vault_name' => null,
123
        ]);
124
    }
125
 
126
    protected function loadUploadWorkflowInfo()
127
    {
128
        return [
129
            'command' => [
130
                'initiate' => 'InitiateMultipartUpload',
131
                'upload'   => 'UploadMultipartPart',
132
                'complete' => 'CompleteMultipartUpload',
133
            ],
134
            'id' => [
135
                'account_id' => 'accountId',
136
                'vault_name' => 'vaultName',
137
                'upload_id'  => 'uploadId',
138
            ],
139
            'part_num' => 'range',
140
        ];
141
    }
142
 
143
    protected function determinePartSize()
144
    {
145
        // Make sure the part size is set.
146
        $partSize = $this->config['part_size'] ?: self::PART_MIN_SIZE;
147
 
148
        // Ensure that the part size is valid.
149
        if (!in_array($partSize, self::$validPartSizes)) {
150
            throw new \InvalidArgumentException('The part_size must be a power '
151
                . 'of 2, in megabytes, such that 1 MB <= PART_SIZE <= 4 GB.');
152
        }
153
 
154
        return $partSize;
155
    }
156
 
157
    protected function createPart($seekable, $number)
158
    {
159
        $data = [];
160
        $firstByte = $this->source->tell();
161
 
162
        // Read from the source to create the body stream. This also
163
        // calculates the linear and tree hashes as the data is read.
164
        if ($seekable) {
165
            // Case 1: Stream is seekable, can make stream from new handle.
166
            $body = Psr7\Utils::tryFopen($this->source->getMetadata('uri'), 'r');
167
            $body = $this->limitPartStream(Psr7\Utils::streamFor($body));
168
            // Create another stream decorated with hashing streams and read
169
            // through it, so we can get the hash values for the part.
170
            $decoratedBody = $this->decorateWithHashes($body, $data);
171
            while (!$decoratedBody->eof()) $decoratedBody->read(1048576);
172
            // Seek the original source forward to the end of the range.
173
            $this->source->seek($this->source->tell() + $body->getSize());
174
        } else {
175
            // Case 2: Stream is not seekable, must store part in temp stream.
176
            $source = $this->limitPartStream($this->source);
177
            $source = $this->decorateWithHashes($source, $data);
178
            $body = Psr7\Utils::streamFor();
179
            Psr7\Utils::copyToStream($source, $body);
180
        }
181
 
182
        // Do not create a part if the body size is zero.
183
        if ($body->getSize() === 0) {
184
            return false;
185
        }
186
 
187
        $body->seek(0);
188
        $data['body'] = $body;
189
        $lastByte = $this->source->tell() - 1;
190
        $data['range'] = "bytes {$firstByte}-{$lastByte}/*";
191
 
192
        return $data;
193
    }
194
 
195
    protected function handleResult(CommandInterface $command, ResultInterface $result)
196
    {
197
        list($rangeIndex, $rangeSize) = $this->parseRange(
198
            $command['range'],
199
            $this->state->getPartSize()
200
        );
201
 
202
        $this->state->markPartAsUploaded($rangeIndex, [
203
            'size'     => $rangeSize,
204
            'checksum' => $command['checksum']
205
        ]);
206
    }
207
 
208
    protected function getInitiateParams()
209
    {
210
        $params = ['partSize' => $this->state->getPartSize()];
211
        if (isset($this->config['archive_description'])) {
212
            $params['archiveDescription'] = $this->config['archive_description'];
213
        }
214
 
215
        return $params;
216
    }
217
 
218
    protected function getCompleteParams()
219
    {
220
        $treeHash = new TreeHash();
221
        $archiveSize = 0;
222
        foreach ($this->state->getUploadedParts() as $part) {
223
            $archiveSize += $part['size'];
224
            $treeHash->addChecksum($part['checksum']);
225
        }
226
 
227
        return [
228
            'archiveSize' => $archiveSize,
229
            'checksum'    => bin2hex($treeHash->complete()),
230
        ];
231
    }
232
 
233
    /**
234
     * Decorates a stream with a tree AND linear sha256 hashing stream.
235
     *
236
     * @param Stream $stream Stream to decorate.
237
     * @param array  $data   Data bag that results are injected into.
238
     *
239
     * @return Stream
240
     */
241
    private function decorateWithHashes(Stream $stream, array &$data)
242
    {
243
        // Make sure that a tree hash is calculated.
244
        $stream = new HashingStream($stream, new TreeHash(),
245
            function ($result) use (&$data) {
246
                $data['checksum'] = bin2hex($result);
247
            }
248
        );
249
 
250
        // Make sure that a linear SHA256 hash is calculated.
251
        $stream = new HashingStream($stream, new PhpHash('sha256'),
252
            function ($result) use (&$data) {
253
                $data['ContentSHA256'] = bin2hex($result);
254
            }
255
        );
256
 
257
        return $stream;
258
    }
259
 
260
    /**
261
     * Parses a Glacier range string into a size and part number.
262
     *
263
     * @param string $range    Glacier range string (e.g., "bytes 5-5000/*")
264
     * @param int    $partSize The chosen part size
265
     *
266
     * @return array
267
     */
268
    private static function parseRange($range, $partSize)
269
    {
270
        // Strip away the prefix and suffix.
271
        if (strpos($range, 'bytes') !== false) {
272
            $range = substr($range, 6, -2);
273
        }
274
 
275
        // Split that range into it's parts.
276
        list($firstByte, $lastByte) = explode('-', $range);
277
 
278
        // Calculate and return range index and range size
279
        return [
280
            intval($firstByte / $partSize) + 1,
281
            $lastByte - $firstByte + 1,
282
        ];
283
    }
284
}