aws-sdk-resources 2.3.23 → 3.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/bin/aws-v3.rb +201 -0
- data/lib/aws-sdk-resources.rb +223 -87
- metadata +3073 -71
- data/lib/aws-sdk-resources/batch.rb +0 -143
- data/lib/aws-sdk-resources/builder.rb +0 -85
- data/lib/aws-sdk-resources/builder_sources.rb +0 -105
- data/lib/aws-sdk-resources/collection.rb +0 -107
- data/lib/aws-sdk-resources/definition.rb +0 -331
- data/lib/aws-sdk-resources/documenter.rb +0 -70
- data/lib/aws-sdk-resources/documenter/base_operation_documenter.rb +0 -279
- data/lib/aws-sdk-resources/documenter/data_operation_documenter.rb +0 -25
- data/lib/aws-sdk-resources/documenter/has_many_operation_documenter.rb +0 -67
- data/lib/aws-sdk-resources/documenter/has_operation_documenter.rb +0 -66
- data/lib/aws-sdk-resources/documenter/operation_documenter.rb +0 -20
- data/lib/aws-sdk-resources/documenter/resource_operation_documenter.rb +0 -53
- data/lib/aws-sdk-resources/documenter/waiter_operation_documenter.rb +0 -77
- data/lib/aws-sdk-resources/errors.rb +0 -15
- data/lib/aws-sdk-resources/operation_methods.rb +0 -83
- data/lib/aws-sdk-resources/operations.rb +0 -280
- data/lib/aws-sdk-resources/options.rb +0 -17
- data/lib/aws-sdk-resources/request.rb +0 -39
- data/lib/aws-sdk-resources/request_params.rb +0 -140
- data/lib/aws-sdk-resources/resource.rb +0 -243
- data/lib/aws-sdk-resources/services/ec2.rb +0 -21
- data/lib/aws-sdk-resources/services/ec2/instance.rb +0 -29
- data/lib/aws-sdk-resources/services/iam.rb +0 -19
- data/lib/aws-sdk-resources/services/s3.rb +0 -19
- data/lib/aws-sdk-resources/services/s3/bucket.rb +0 -127
- data/lib/aws-sdk-resources/services/s3/encryption.rb +0 -21
- data/lib/aws-sdk-resources/services/s3/encryption/client.rb +0 -369
- data/lib/aws-sdk-resources/services/s3/encryption/decrypt_handler.rb +0 -174
- data/lib/aws-sdk-resources/services/s3/encryption/default_cipher_provider.rb +0 -63
- data/lib/aws-sdk-resources/services/s3/encryption/default_key_provider.rb +0 -38
- data/lib/aws-sdk-resources/services/s3/encryption/encrypt_handler.rb +0 -50
- data/lib/aws-sdk-resources/services/s3/encryption/errors.rb +0 -13
- data/lib/aws-sdk-resources/services/s3/encryption/io_auth_decrypter.rb +0 -50
- data/lib/aws-sdk-resources/services/s3/encryption/io_decrypter.rb +0 -29
- data/lib/aws-sdk-resources/services/s3/encryption/io_encrypter.rb +0 -69
- data/lib/aws-sdk-resources/services/s3/encryption/key_provider.rb +0 -29
- data/lib/aws-sdk-resources/services/s3/encryption/kms_cipher_provider.rb +0 -71
- data/lib/aws-sdk-resources/services/s3/encryption/materials.rb +0 -58
- data/lib/aws-sdk-resources/services/s3/encryption/utils.rb +0 -79
- data/lib/aws-sdk-resources/services/s3/file_part.rb +0 -75
- data/lib/aws-sdk-resources/services/s3/file_uploader.rb +0 -58
- data/lib/aws-sdk-resources/services/s3/multipart_file_uploader.rb +0 -187
- data/lib/aws-sdk-resources/services/s3/multipart_upload.rb +0 -42
- data/lib/aws-sdk-resources/services/s3/multipart_upload_error.rb +0 -16
- data/lib/aws-sdk-resources/services/s3/object.rb +0 -257
- data/lib/aws-sdk-resources/services/s3/object_copier.rb +0 -99
- data/lib/aws-sdk-resources/services/s3/object_multipart_copier.rb +0 -179
- data/lib/aws-sdk-resources/services/s3/object_summary.rb +0 -65
- data/lib/aws-sdk-resources/services/s3/presigned_post.rb +0 -647
- data/lib/aws-sdk-resources/services/sns.rb +0 -7
- data/lib/aws-sdk-resources/services/sns/message_verifier.rb +0 -157
- data/lib/aws-sdk-resources/services/sqs.rb +0 -7
- data/lib/aws-sdk-resources/services/sqs/queue_poller.rb +0 -521
- data/lib/aws-sdk-resources/source.rb +0 -39
@@ -1,42 +0,0 @@
|
|
1
|
-
module Aws
|
2
|
-
module S3
|
3
|
-
class MultipartUpload
|
4
|
-
|
5
|
-
alias_method :basic_complete, :complete
|
6
|
-
|
7
|
-
# Completes the upload, requires a list of completed parts. You can
|
8
|
-
# provide the list of parts with `:part_number` and `:etag` values.
|
9
|
-
#
|
10
|
-
# upload.complete(multipart_upload: { parts: [
|
11
|
-
# { part_number: 1, etag:'etag1' },
|
12
|
-
# { part_number: 2, etag:'etag2' },
|
13
|
-
# ...
|
14
|
-
# ]})
|
15
|
-
#
|
16
|
-
# Alternatively, you can pass **`compute_parts: true`** and the part
|
17
|
-
# list will be computed by calling {Client#list_parts}.
|
18
|
-
#
|
19
|
-
# upload.complete(compute_parts: true)
|
20
|
-
#
|
21
|
-
# @option options [Boolean] :compute_parts (false) When `true`,
|
22
|
-
# the {Client#list_parts} method will be called to determine
|
23
|
-
# the list of required part numbers and their ETags.
|
24
|
-
#
|
25
|
-
def complete(options = {})
|
26
|
-
if options.delete(:compute_parts)
|
27
|
-
options[:multipart_upload] = { parts: compute_parts }
|
28
|
-
end
|
29
|
-
basic_complete(options)
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def compute_parts
|
35
|
-
parts.sort_by(&:part_number).each.with_object([]) do |part, part_list|
|
36
|
-
part_list << { part_number: part.part_number, etag: part.etag }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module Aws
|
2
|
-
module S3
|
3
|
-
class MultipartUploadError < StandardError
|
4
|
-
|
5
|
-
def initialize(message, errors)
|
6
|
-
@errors = errors
|
7
|
-
super(message)
|
8
|
-
end
|
9
|
-
|
10
|
-
# @return [Array<StandardError>] The list of errors encountered
|
11
|
-
# when uploading or aborting the upload.
|
12
|
-
attr_reader :errors
|
13
|
-
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,257 +0,0 @@
|
|
1
|
-
module Aws
|
2
|
-
module S3
|
3
|
-
class Object
|
4
|
-
|
5
|
-
alias size content_length
|
6
|
-
|
7
|
-
# Copies another object to this object. Use `multipart_copy: true`
|
8
|
-
# for large objects. This is required for objects that exceed 5GB.
|
9
|
-
#
|
10
|
-
# @param [S3::Object, S3::ObjectVersion, S3::ObjectSummary, String, Hash] source
|
11
|
-
# Where to copy object data from. `source` must be one of the following:
|
12
|
-
#
|
13
|
-
# * {Aws::S3::Object}
|
14
|
-
# * {Aws::S3::ObjectSummary}
|
15
|
-
# * {Aws::S3::ObjectVersion}
|
16
|
-
# * Hash - with `:bucket` and `:key` and optional `:version_id`
|
17
|
-
# * String - formatted like `"source-bucket-name/uri-escaped-key"`
|
18
|
-
# or `"source-bucket-name/uri-escaped-key?versionId=version-id"`
|
19
|
-
#
|
20
|
-
# @option options [Boolean] :multipart_copy (false) When `true`,
|
21
|
-
# the object will be copied using the multipart APIs. This is
|
22
|
-
# necessary for objects larger than 5GB and can provide
|
23
|
-
# performance improvements on large objects. Amazon S3 does
|
24
|
-
# not accept multipart copies for objects smaller than 5MB.
|
25
|
-
#
|
26
|
-
# @option options [Integer] :content_length Only used when
|
27
|
-
# `:multipart_copy` is `true`. Passing this options avoids a HEAD
|
28
|
-
# request to query the source object size.
|
29
|
-
#
|
30
|
-
# @option options [S3::Client] :copy_source_client Only used when
|
31
|
-
# `:multipart_copy` is `true` and the source object is in a
|
32
|
-
# different region. You do not need to specify this option
|
33
|
-
# if you have provided `:content_length`.
|
34
|
-
#
|
35
|
-
# @option options [String] :copy_source_region Only used when
|
36
|
-
# `:multipart_copy` is `true` and the source object is in a
|
37
|
-
# different region. You do not need to specify this option
|
38
|
-
# if you have provided a `:source_client` or a `:content_length`.
|
39
|
-
#
|
40
|
-
# @example Basic object copy
|
41
|
-
#
|
42
|
-
# bucket = Aws::S3::Bucket.new('target-bucket')
|
43
|
-
# object = bucket.object('target-key')
|
44
|
-
#
|
45
|
-
# # source as String
|
46
|
-
# object.copy_from('source-bucket/source-key')
|
47
|
-
#
|
48
|
-
# # source as Hash
|
49
|
-
# object.copy_from(bucket:'source-bucket', key:'source-key')
|
50
|
-
#
|
51
|
-
# # source as Aws::S3::Object
|
52
|
-
# object.copy_from(bucket.object('source-key'))
|
53
|
-
#
|
54
|
-
# @example Managed copy of large objects
|
55
|
-
#
|
56
|
-
# # uses multipart upload APIs to copy object
|
57
|
-
# object.copy_from('src-bucket/src-key', multipart_copy: true)
|
58
|
-
#
|
59
|
-
# @see #copy_to
|
60
|
-
#
|
61
|
-
def copy_from(source, options = {})
|
62
|
-
if Hash === source && source[:copy_source]
|
63
|
-
# for backwards compatibility
|
64
|
-
@client.copy_object(source.merge(bucket: bucket_name, key: key))
|
65
|
-
else
|
66
|
-
ObjectCopier.new(self, options).copy_from(source, options)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Copies this object to another object. Use `multipart_copy: true`
|
71
|
-
# for large objects. This is required for objects that exceed 5GB.
|
72
|
-
#
|
73
|
-
# @note If you need to copy to a bucket in a different region, use
|
74
|
-
# {#copy_from}.
|
75
|
-
#
|
76
|
-
# @param [S3::Object, String, Hash] target Where to copy the object
|
77
|
-
# data to. `target` must be one of the following:
|
78
|
-
#
|
79
|
-
# * {Aws::S3::Object}
|
80
|
-
# * Hash - with `:bucket` and `:key`
|
81
|
-
# * String - formatted like `"target-bucket-name/target-key"`
|
82
|
-
#
|
83
|
-
# @example Basic object copy
|
84
|
-
#
|
85
|
-
# bucket = Aws::S3::Bucket.new('source-bucket')
|
86
|
-
# object = bucket.object('source-key')
|
87
|
-
#
|
88
|
-
# # target as String
|
89
|
-
# object.copy_to('target-bucket/target-key')
|
90
|
-
#
|
91
|
-
# # target as Hash
|
92
|
-
# object.copy_to(bucket: 'target-bucket', key: 'target-key')
|
93
|
-
#
|
94
|
-
# # target as Aws::S3::Object
|
95
|
-
# object.copy_to(bucket.object('target-key'))
|
96
|
-
#
|
97
|
-
# @example Managed copy of large objects
|
98
|
-
#
|
99
|
-
# # uses multipart upload APIs to copy object
|
100
|
-
# object.copy_to('src-bucket/src-key', multipart_copy: true)
|
101
|
-
#
|
102
|
-
def copy_to(target, options = {})
|
103
|
-
ObjectCopier.new(self, options).copy_to(target, options)
|
104
|
-
end
|
105
|
-
|
106
|
-
# Copies and deletes the current object. The object will only be
|
107
|
-
# deleted if the copy operation succeeds.
|
108
|
-
# @param (see Object#copy_to)
|
109
|
-
# @option (see Object#copy_to)
|
110
|
-
# @return [void]
|
111
|
-
# @see Object#copy_to
|
112
|
-
# @see Object#delete
|
113
|
-
def move_to(target, options = {})
|
114
|
-
copy_to(target, options)
|
115
|
-
delete
|
116
|
-
end
|
117
|
-
|
118
|
-
# Creates a {PresignedPost} that makes it easy to upload a file from
|
119
|
-
# a web browser direct to Amazon S3 using an HTML post form with
|
120
|
-
# a file field.
|
121
|
-
#
|
122
|
-
# See the {PresignedPost} documentation for more information.
|
123
|
-
#
|
124
|
-
# @option (see PresignedPost#initialize)
|
125
|
-
# @return [PresignedPost]
|
126
|
-
# @see PresignedPost
|
127
|
-
def presigned_post(options = {})
|
128
|
-
PresignedPost.new(
|
129
|
-
client.config.credentials,
|
130
|
-
client.config.region,
|
131
|
-
bucket_name,
|
132
|
-
{
|
133
|
-
key: key,
|
134
|
-
url: bucket.url,
|
135
|
-
}.merge(options)
|
136
|
-
)
|
137
|
-
end
|
138
|
-
|
139
|
-
# Generates a pre-signed URL for this object.
|
140
|
-
#
|
141
|
-
# @example Pre-signed GET URL, valid for one hour
|
142
|
-
#
|
143
|
-
# obj.presigned_url(:get, expires_in: 3600)
|
144
|
-
# #=> "https://bucket-name.s3.amazonaws.com/object-key?..."
|
145
|
-
#
|
146
|
-
# @example Pre-signed PUT with a canned ACL
|
147
|
-
#
|
148
|
-
# # the object uploaded using this URL will be publicly accessible
|
149
|
-
# obj.presigned_url(:put, acl: 'public-read')
|
150
|
-
# #=> "https://bucket-name.s3.amazonaws.com/object-key?..."
|
151
|
-
#
|
152
|
-
# @param [Symbol] http_method
|
153
|
-
# The HTTP method to generate a presigned URL for. Valid values
|
154
|
-
# are `:get`, `:put`, `:head`, and `:delete`.
|
155
|
-
#
|
156
|
-
# @param [Hash] params
|
157
|
-
# Additional request parameters to use when generating the pre-signed
|
158
|
-
# URL. See the related documentation in {Client} for accepted
|
159
|
-
# params.
|
160
|
-
#
|
161
|
-
# | HTTP Method | Client Method |
|
162
|
-
# |---------------|------------------------|
|
163
|
-
# | `:get` | {Client#get_object} |
|
164
|
-
# | `:put` | {Client#put_object} |
|
165
|
-
# | `:head` | {Client#head_object} |
|
166
|
-
# | `:delete` | {Client#delete_object} |
|
167
|
-
#
|
168
|
-
# @option params [Boolean] :virtual_host (false) When `true` the
|
169
|
-
# presigned URL will use the bucket name as a virtual host.
|
170
|
-
#
|
171
|
-
# bucket = Aws::S3::Bucket.new('my.bucket.com')
|
172
|
-
# bucket.object('key').presigned_url(virtual_host: true)
|
173
|
-
# #=> "http://my.bucket.com/key?..."
|
174
|
-
#
|
175
|
-
# @option params [Integer] :expires_in (900) Number of seconds before
|
176
|
-
# the pre-signed URL expires. This may not exceed one week (604800
|
177
|
-
# seconds). Note that the pre-signed URL is also only valid as long as
|
178
|
-
# credentials used to sign it are. For example, when using IAM roles,
|
179
|
-
# temporary tokens generated for signing also have a default expiration
|
180
|
-
# which will affect the effective expiration of the pre-signed URL.
|
181
|
-
#
|
182
|
-
# @raise [ArgumentError] Raised if `:expires_in` exceeds one week
|
183
|
-
# (604800 seconds).
|
184
|
-
#
|
185
|
-
# @return [String]
|
186
|
-
#
|
187
|
-
def presigned_url(http_method, params = {})
|
188
|
-
presigner = Presigner.new(client: client)
|
189
|
-
presigner.presigned_url("#{http_method.downcase}_object", params.merge(
|
190
|
-
bucket: bucket_name,
|
191
|
-
key: key,
|
192
|
-
))
|
193
|
-
end
|
194
|
-
|
195
|
-
# Returns the public (un-signed) URL for this object.
|
196
|
-
#
|
197
|
-
# s3.bucket('bucket-name').object('obj-key').public_url
|
198
|
-
# #=> "https://bucket-name.s3.amazonaws.com/obj-key"
|
199
|
-
#
|
200
|
-
# To use virtual hosted bucket url (disables https):
|
201
|
-
#
|
202
|
-
# s3.bucket('my.bucket.com').object('key').public_url(virtual_host: true)
|
203
|
-
# #=> "http://my.bucket.com/key"
|
204
|
-
#
|
205
|
-
# @option options [Boolean] :virtual_host (false) When `true`, the bucket
|
206
|
-
# name will be used as the host name. This is useful when you have
|
207
|
-
# a CNAME configured for the bucket.
|
208
|
-
#
|
209
|
-
# @return [String]
|
210
|
-
def public_url(options = {})
|
211
|
-
url = URI.parse(bucket.url(options))
|
212
|
-
url.path += '/' unless url.path[-1] == '/'
|
213
|
-
url.path += key.gsub(/[^\/]+/) { |s| Seahorse::Util.uri_escape(s) }
|
214
|
-
url.to_s
|
215
|
-
end
|
216
|
-
|
217
|
-
# Uploads a file from disk to the current object in S3.
|
218
|
-
#
|
219
|
-
# # small files are uploaded in a single API call
|
220
|
-
# obj.upload_file('/path/to/file')
|
221
|
-
#
|
222
|
-
# Files larger than `:multipart_threshold` are uploaded using the
|
223
|
-
# Amazon S3 multipart upload APIs.
|
224
|
-
#
|
225
|
-
# # large files are automatically split into parts
|
226
|
-
# # and the parts are uploaded in parallel
|
227
|
-
# obj.upload_file('/path/to/very_large_file')
|
228
|
-
#
|
229
|
-
# @param [String,Pathname,File,Tempfile] source A file or path to a file
|
230
|
-
# on the local file system that should be uploaded to this object.
|
231
|
-
# If you pass an open file object, then it is your responsibility
|
232
|
-
# to close the file object once the upload completes.
|
233
|
-
#
|
234
|
-
# @option options [Integer] :multipart_threshold (15728640) Files larger
|
235
|
-
# than `:multipart_threshold` are uploaded using the S3 multipart APIs.
|
236
|
-
# Default threshold is 15MB.
|
237
|
-
#
|
238
|
-
# @raise [MultipartUploadError] If an object is being uploaded in
|
239
|
-
# parts, and the upload can not be completed, then the upload is
|
240
|
-
# aborted and this error is raised. The raised error has a `#errors`
|
241
|
-
# method that returns the failures that caused the upload to be
|
242
|
-
# aborted.
|
243
|
-
#
|
244
|
-
# @return [Boolean] Returns `true` when the object is uploaded
|
245
|
-
# without any errors.
|
246
|
-
#
|
247
|
-
def upload_file(source, options = {})
|
248
|
-
uploader = FileUploader.new(
|
249
|
-
multipart_threshold: options.delete(:multipart_threshold),
|
250
|
-
client: client)
|
251
|
-
uploader.upload(source, options.merge(bucket: bucket_name, key: key))
|
252
|
-
true
|
253
|
-
end
|
254
|
-
|
255
|
-
end
|
256
|
-
end
|
257
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
module Aws
|
4
|
-
module S3
|
5
|
-
# @api private
|
6
|
-
class ObjectCopier
|
7
|
-
|
8
|
-
# @param [S3::Object] object
|
9
|
-
def initialize(object, options = {})
|
10
|
-
@object = object
|
11
|
-
@options = options.merge(client: @object.client)
|
12
|
-
end
|
13
|
-
|
14
|
-
def copy_from(source, options = {})
|
15
|
-
copy_object(source, @object, merge_options(source, options))
|
16
|
-
end
|
17
|
-
|
18
|
-
def copy_to(target, options = {})
|
19
|
-
copy_object(@object, target, merge_options(target, options))
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def copy_object(source, target, options)
|
25
|
-
target_bucket, target_key = copy_target(target)
|
26
|
-
options[:bucket] = target_bucket
|
27
|
-
options[:key] = target_key
|
28
|
-
options[:copy_source] = copy_source(source)
|
29
|
-
if options.delete(:multipart_copy)
|
30
|
-
apply_source_client(source, options)
|
31
|
-
ObjectMultipartCopier.new(@options).copy(options)
|
32
|
-
else
|
33
|
-
@object.client.copy_object(options)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def copy_source(source)
|
38
|
-
case source
|
39
|
-
when String then source
|
40
|
-
when Hash
|
41
|
-
src = "#{source[:bucket]}/#{escape(source[:key])}"
|
42
|
-
src += "?versionId=#{source[:version_id]}" if source.key?(:version_id)
|
43
|
-
src
|
44
|
-
when S3::Object, S3::ObjectSummary
|
45
|
-
"#{source.bucket_name}/#{escape(source.key)}"
|
46
|
-
when S3::ObjectVersion
|
47
|
-
"#{source.bucket_name}/#{escape(source.object_key)}?versionId=#{source.id}"
|
48
|
-
else
|
49
|
-
msg = "expected source to be an Aws::S3::Object, Hash, or String"
|
50
|
-
raise ArgumentError, msg
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def copy_target(target)
|
55
|
-
case target
|
56
|
-
when String then target.match(/([^\/]+?)\/(.+)/)[1,2]
|
57
|
-
when Hash then target.values_at(:bucket, :key)
|
58
|
-
when S3::Object then [target.bucket_name, target.key]
|
59
|
-
else
|
60
|
-
msg = "expected target to be an Aws::S3::Object, Hash, or String"
|
61
|
-
raise ArgumentError, msg
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def merge_options(source_or_target, options)
|
66
|
-
if Hash === source_or_target
|
67
|
-
source_or_target.inject(options.dup) do |opts, (key, value)|
|
68
|
-
opts[key] = value unless [:bucket, :key, :version_id].include?(key)
|
69
|
-
opts
|
70
|
-
end
|
71
|
-
else
|
72
|
-
options.dup
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def apply_source_client(source, options)
|
77
|
-
|
78
|
-
if source.respond_to?(:client)
|
79
|
-
options[:copy_source_client] ||= source.client
|
80
|
-
end
|
81
|
-
|
82
|
-
if options[:copy_source_region]
|
83
|
-
config = @object.client.config
|
84
|
-
config = config.each_pair.inject({}) { |h, (k,v)| h[k] = v; h }
|
85
|
-
config[:region] = options.delete(:copy_source_region)
|
86
|
-
options[:copy_source_client] ||= S3::Client.new(config)
|
87
|
-
end
|
88
|
-
|
89
|
-
options[:copy_source_client] ||= @object.client
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
def escape(str)
|
94
|
-
Seahorse::Util.uri_path_escape(str)
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
@@ -1,179 +0,0 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
module Aws
|
4
|
-
module S3
|
5
|
-
# @api private
|
6
|
-
class ObjectMultipartCopier
|
7
|
-
|
8
|
-
FIVE_MB = 5 * 1024 * 1024 # 5MB
|
9
|
-
|
10
|
-
FILE_TOO_SMALL = "unable to multipart copy files smaller than 5MB"
|
11
|
-
|
12
|
-
MAX_PARTS = 10_000
|
13
|
-
|
14
|
-
# @option options [Client] :client
|
15
|
-
# @option [Integer] :min_part_size (52428800) Size of copied parts.
|
16
|
-
# Defaults to 50MB.
|
17
|
-
# will be constructed from the given `options' hash.
|
18
|
-
# @option [Integer] :thread_count (10) Number of concurrent threads to
|
19
|
-
# use for copying parts.
|
20
|
-
def initialize(options = {})
|
21
|
-
@thread_count = options.delete(:thread_count) || 10
|
22
|
-
@min_part_size = options.delete(:min_part_size) || (FIVE_MB * 10)
|
23
|
-
@client = options[:client] || Client.new
|
24
|
-
end
|
25
|
-
|
26
|
-
# @return [Client]
|
27
|
-
attr_reader :client
|
28
|
-
|
29
|
-
# @option (see S3::Client#copy_object)
|
30
|
-
def copy(options = {})
|
31
|
-
size = source_size(options)
|
32
|
-
options[:upload_id] = initiate_upload(options)
|
33
|
-
begin
|
34
|
-
parts = copy_parts(size, default_part_size(size), options)
|
35
|
-
complete_upload(parts, options)
|
36
|
-
rescue => error
|
37
|
-
abort_upload(options)
|
38
|
-
raise error
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def initiate_upload(options)
|
45
|
-
options = options_for(:create_multipart_upload, options)
|
46
|
-
@client.create_multipart_upload(options).upload_id
|
47
|
-
end
|
48
|
-
|
49
|
-
def copy_parts(size, default_part_size, options)
|
50
|
-
queue = PartQueue.new(compute_parts(size, default_part_size, options))
|
51
|
-
threads = []
|
52
|
-
@thread_count.times do
|
53
|
-
threads << copy_part_thread(queue)
|
54
|
-
end
|
55
|
-
threads.map(&:value).flatten.sort_by{ |part| part[:part_number] }
|
56
|
-
end
|
57
|
-
|
58
|
-
def copy_part_thread(queue)
|
59
|
-
Thread.new do
|
60
|
-
begin
|
61
|
-
completed = []
|
62
|
-
while part = queue.shift
|
63
|
-
completed << copy_part(part)
|
64
|
-
end
|
65
|
-
completed
|
66
|
-
rescue => error
|
67
|
-
queue.clear!
|
68
|
-
raise error
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def copy_part(part)
|
74
|
-
{
|
75
|
-
etag: @client.upload_part_copy(part).copy_part_result.etag,
|
76
|
-
part_number: part[:part_number],
|
77
|
-
}
|
78
|
-
end
|
79
|
-
|
80
|
-
def complete_upload(parts, options)
|
81
|
-
options = options_for(:complete_multipart_upload, options)
|
82
|
-
options[:multipart_upload] = { parts: parts }
|
83
|
-
@client.complete_multipart_upload(options)
|
84
|
-
end
|
85
|
-
|
86
|
-
def abort_upload(options)
|
87
|
-
@client.abort_multipart_upload({
|
88
|
-
bucket: options[:bucket],
|
89
|
-
key: options[:key],
|
90
|
-
upload_id: options[:upload_id],
|
91
|
-
})
|
92
|
-
end
|
93
|
-
|
94
|
-
def compute_parts(size, default_part_size, options)
|
95
|
-
part_number = 1
|
96
|
-
offset = 0
|
97
|
-
parts = []
|
98
|
-
options = options_for(:upload_part_copy, options)
|
99
|
-
while offset < size
|
100
|
-
parts << options.merge({
|
101
|
-
part_number: part_number,
|
102
|
-
copy_source_range: byte_range(offset, default_part_size, size),
|
103
|
-
})
|
104
|
-
part_number += 1
|
105
|
-
offset += default_part_size
|
106
|
-
end
|
107
|
-
parts
|
108
|
-
end
|
109
|
-
|
110
|
-
def byte_range(offset, default_part_size, size)
|
111
|
-
if offset + default_part_size < size
|
112
|
-
"bytes=#{offset}-#{offset + default_part_size - 1}"
|
113
|
-
else
|
114
|
-
"bytes=#{offset}-#{size - 1}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def source_size(options)
|
119
|
-
return options.delete(:content_length) if options[:content_length]
|
120
|
-
|
121
|
-
client = options[:copy_source_client] || @client
|
122
|
-
|
123
|
-
if vid_match = options[:copy_source].match(/([^\/]+?)\/(.+)\?versionId=(.+)/)
|
124
|
-
bucket, key, version_id = vid_match[1,3]
|
125
|
-
else
|
126
|
-
bucket, key = options[:copy_source].match(/([^\/]+?)\/(.+)/)[1,2]
|
127
|
-
end
|
128
|
-
|
129
|
-
key = CGI.unescape(key)
|
130
|
-
opts = { bucket: bucket, key: key }
|
131
|
-
opts[:version_id] = version_id if version_id
|
132
|
-
client.head_object(opts).content_length
|
133
|
-
end
|
134
|
-
|
135
|
-
def default_part_size(source_size)
|
136
|
-
if source_size < FIVE_MB
|
137
|
-
raise ArgumentError, FILE_TOO_SMALL
|
138
|
-
else
|
139
|
-
[(source_size.to_f / MAX_PARTS).ceil, @min_part_size].max.to_i
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def options_for(operation_name, options)
|
144
|
-
API_OPTIONS[operation_name].inject({}) do |hash, opt_name|
|
145
|
-
hash[opt_name] = options[opt_name] if options.key?(opt_name)
|
146
|
-
hash
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
# @api private
|
151
|
-
def self.options_for(shape_name)
|
152
|
-
Client.api.metadata['shapes'][shape_name].member_names
|
153
|
-
end
|
154
|
-
|
155
|
-
API_OPTIONS = {
|
156
|
-
create_multipart_upload: options_for('CreateMultipartUploadRequest'),
|
157
|
-
upload_part_copy: options_for('UploadPartCopyRequest'),
|
158
|
-
complete_multipart_upload: options_for('CompleteMultipartUploadRequest'),
|
159
|
-
}
|
160
|
-
|
161
|
-
class PartQueue
|
162
|
-
|
163
|
-
def initialize(parts = [])
|
164
|
-
@parts = parts
|
165
|
-
@mutex = Mutex.new
|
166
|
-
end
|
167
|
-
|
168
|
-
def shift
|
169
|
-
@mutex.synchronize { @parts.shift }
|
170
|
-
end
|
171
|
-
|
172
|
-
def clear!
|
173
|
-
@mutex.synchronize { @parts.clear }
|
174
|
-
end
|
175
|
-
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|