aws-sdk-s3 1.103.0 → 1.202.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 +4 -4
- data/CHANGELOG.md +664 -0
- data/VERSION +1 -1
- data/lib/aws-sdk-s3/access_grants_credentials.rb +57 -0
- data/lib/aws-sdk-s3/access_grants_credentials_provider.rb +250 -0
- data/lib/aws-sdk-s3/bucket.rb +858 -116
- data/lib/aws-sdk-s3/bucket_acl.rb +32 -9
- data/lib/aws-sdk-s3/bucket_cors.rb +38 -13
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +43 -12
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +100 -13
- data/lib/aws-sdk-s3/bucket_logging.rb +35 -6
- data/lib/aws-sdk-s3/bucket_notification.rb +27 -9
- data/lib/aws-sdk-s3/bucket_policy.rb +79 -10
- data/lib/aws-sdk-s3/bucket_region_cache.rb +9 -5
- data/lib/aws-sdk-s3/bucket_request_payment.rb +29 -7
- data/lib/aws-sdk-s3/bucket_tagging.rb +35 -11
- data/lib/aws-sdk-s3/bucket_versioning.rb +108 -17
- data/lib/aws-sdk-s3/bucket_website.rb +35 -11
- data/lib/aws-sdk-s3/client.rb +11799 -3636
- data/lib/aws-sdk-s3/client_api.rb +1201 -276
- data/lib/aws-sdk-s3/customizations/bucket.rb +23 -47
- data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
- data/lib/aws-sdk-s3/customizations/object.rb +216 -70
- data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
- data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
- data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
- data/lib/aws-sdk-s3/customizations.rb +27 -29
- data/lib/aws-sdk-s3/default_executor.rb +103 -0
- data/lib/aws-sdk-s3/encryption/client.rb +6 -2
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +13 -9
- data/lib/aws-sdk-s3/encryptionV2/client.rb +6 -2
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +1 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +10 -6
- data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
- data/lib/aws-sdk-s3/endpoint_provider.rb +716 -0
- data/lib/aws-sdk-s3/endpoints.rb +1518 -0
- data/lib/aws-sdk-s3/errors.rb +58 -0
- data/lib/aws-sdk-s3/express_credentials.rb +55 -0
- data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
- data/lib/aws-sdk-s3/file_downloader.rb +241 -87
- data/lib/aws-sdk-s3/file_uploader.rb +16 -13
- data/lib/aws-sdk-s3/legacy_signer.rb +2 -1
- data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +108 -86
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +110 -92
- data/lib/aws-sdk-s3/multipart_upload.rb +294 -19
- data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
- data/lib/aws-sdk-s3/multipart_upload_part.rb +297 -31
- data/lib/aws-sdk-s3/object.rb +2224 -269
- data/lib/aws-sdk-s3/object_acl.rb +59 -17
- data/lib/aws-sdk-s3/object_copier.rb +7 -5
- data/lib/aws-sdk-s3/object_multipart_copier.rb +48 -23
- data/lib/aws-sdk-s3/object_summary.rb +1915 -220
- data/lib/aws-sdk-s3/object_version.rb +450 -58
- data/lib/aws-sdk-s3/plugins/accelerate.rb +3 -44
- data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
- data/lib/aws-sdk-s3/plugins/arn.rb +0 -197
- data/lib/aws-sdk-s3/plugins/bucket_dns.rb +3 -39
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +1 -6
- data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
- data/lib/aws-sdk-s3/plugins/dualstack.rb +1 -55
- data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +2 -1
- data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +55 -18
- data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +6 -29
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +3 -1
- data/lib/aws-sdk-s3/plugins/md5s.rb +10 -68
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +42 -111
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +28 -9
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
- data/lib/aws-sdk-s3/presigned_post.rb +99 -78
- data/lib/aws-sdk-s3/presigner.rb +32 -41
- data/lib/aws-sdk-s3/resource.rb +139 -12
- data/lib/aws-sdk-s3/transfer_manager.rb +304 -0
- data/lib/aws-sdk-s3/types.rb +10204 -5378
- data/lib/aws-sdk-s3.rb +35 -27
- data/sig/bucket.rbs +231 -0
- data/sig/bucket_acl.rbs +78 -0
- data/sig/bucket_cors.rbs +69 -0
- data/sig/bucket_lifecycle.rbs +88 -0
- data/sig/bucket_lifecycle_configuration.rbs +115 -0
- data/sig/bucket_logging.rbs +76 -0
- data/sig/bucket_notification.rbs +114 -0
- data/sig/bucket_policy.rbs +59 -0
- data/sig/bucket_request_payment.rbs +54 -0
- data/sig/bucket_tagging.rbs +65 -0
- data/sig/bucket_versioning.rbs +77 -0
- data/sig/bucket_website.rbs +93 -0
- data/sig/client.rbs +2586 -0
- data/sig/customizations/bucket.rbs +19 -0
- data/sig/customizations/object.rbs +38 -0
- data/sig/customizations/object_summary.rbs +35 -0
- data/sig/errors.rbs +44 -0
- data/sig/multipart_upload.rbs +120 -0
- data/sig/multipart_upload_part.rbs +109 -0
- data/sig/object.rbs +464 -0
- data/sig/object_acl.rbs +86 -0
- data/sig/object_summary.rbs +347 -0
- data/sig/object_version.rbs +143 -0
- data/sig/resource.rbs +141 -0
- data/sig/types.rbs +2868 -0
- data/sig/waiters.rbs +95 -0
- metadata +51 -16
- data/lib/aws-sdk-s3/arn/access_point_arn.rb +0 -69
- data/lib/aws-sdk-s3/arn/multi_region_access_point_arn.rb +0 -69
- data/lib/aws-sdk-s3/arn/object_lambda_arn.rb +0 -69
- data/lib/aws-sdk-s3/arn/outpost_access_point_arn.rb +0 -73
- data/lib/aws-sdk-s3/plugins/object_lambda_endpoint.rb +0 -25
|
@@ -7,30 +7,21 @@ module Aws
|
|
|
7
7
|
module S3
|
|
8
8
|
# @api private
|
|
9
9
|
class MultipartFileUploader
|
|
10
|
-
|
|
11
10
|
MIN_PART_SIZE = 5 * 1024 * 1024 # 5MB
|
|
12
|
-
|
|
13
|
-
FILE_TOO_SMALL = "unable to multipart upload files smaller than 5MB"
|
|
14
|
-
|
|
15
11
|
MAX_PARTS = 10_000
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# @api private
|
|
25
|
-
UPLOAD_PART_OPTIONS = Set.new(
|
|
26
|
-
Client.api.operation(:upload_part).input.shape.member_names
|
|
12
|
+
CREATE_OPTIONS = Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
|
|
13
|
+
COMPLETE_OPTIONS = Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
|
|
14
|
+
UPLOAD_PART_OPTIONS = Set.new(Client.api.operation(:upload_part).input.shape.member_names)
|
|
15
|
+
CHECKSUM_KEYS = Set.new(
|
|
16
|
+
Client.api.operation(:upload_part).input.shape.members.map do |n, s|
|
|
17
|
+
n if s.location == 'header' && s.location_name.start_with?('x-amz-checksum-')
|
|
18
|
+
end.compact
|
|
27
19
|
)
|
|
28
20
|
|
|
29
21
|
# @option options [Client] :client
|
|
30
|
-
# @option options [Integer] :thread_count (THREAD_COUNT)
|
|
31
22
|
def initialize(options = {})
|
|
32
23
|
@client = options[:client] || Client.new
|
|
33
|
-
@
|
|
24
|
+
@executor = options[:executor]
|
|
34
25
|
end
|
|
35
26
|
|
|
36
27
|
# @return [Client]
|
|
@@ -42,15 +33,14 @@ module Aws
|
|
|
42
33
|
# @option options [Proc] :progress_callback
|
|
43
34
|
# A Proc that will be called when each chunk of the upload is sent.
|
|
44
35
|
# It will be invoked with [bytes_read], [total_sizes]
|
|
45
|
-
# @return [
|
|
36
|
+
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
|
46
37
|
def upload(source, options = {})
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
end
|
|
38
|
+
file_size = File.size(source)
|
|
39
|
+
raise ArgumentError, 'unable to multipart upload files smaller than 5MB' if file_size < MIN_PART_SIZE
|
|
40
|
+
|
|
41
|
+
upload_id = initiate_upload(options)
|
|
42
|
+
parts = upload_parts(upload_id, source, file_size, options)
|
|
43
|
+
complete_upload(upload_id, parts, file_size, options)
|
|
54
44
|
end
|
|
55
45
|
|
|
56
46
|
private
|
|
@@ -59,19 +49,21 @@ module Aws
|
|
|
59
49
|
@client.create_multipart_upload(create_opts(options)).upload_id
|
|
60
50
|
end
|
|
61
51
|
|
|
62
|
-
def complete_upload(upload_id, parts, options)
|
|
52
|
+
def complete_upload(upload_id, parts, file_size, options)
|
|
63
53
|
@client.complete_multipart_upload(
|
|
64
|
-
|
|
65
|
-
key: options[:key],
|
|
54
|
+
**complete_opts(options),
|
|
66
55
|
upload_id: upload_id,
|
|
67
|
-
multipart_upload: { parts: parts }
|
|
56
|
+
multipart_upload: { parts: parts },
|
|
57
|
+
mpu_object_size: file_size
|
|
68
58
|
)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
abort_upload(upload_id, options, [e])
|
|
69
61
|
end
|
|
70
62
|
|
|
71
|
-
def upload_parts(upload_id, source, options)
|
|
72
|
-
pending = PartList.new(compute_parts(upload_id, source, options))
|
|
63
|
+
def upload_parts(upload_id, source, file_size, options)
|
|
73
64
|
completed = PartList.new
|
|
74
|
-
|
|
65
|
+
pending = PartList.new(compute_parts(upload_id, source, file_size, options))
|
|
66
|
+
errors = upload_with_executor(pending, completed, options)
|
|
75
67
|
if errors.empty?
|
|
76
68
|
completed.to_a.sort_by { |part| part[:part_number] }
|
|
77
69
|
else
|
|
@@ -80,34 +72,30 @@ module Aws
|
|
|
80
72
|
end
|
|
81
73
|
|
|
82
74
|
def abort_upload(upload_id, options, errors)
|
|
83
|
-
@client.abort_multipart_upload(
|
|
84
|
-
|
|
85
|
-
key: options[:key],
|
|
86
|
-
upload_id: upload_id
|
|
87
|
-
)
|
|
88
|
-
msg = "multipart upload failed: #{errors.map(&:message).join("; ")}"
|
|
75
|
+
@client.abort_multipart_upload(bucket: options[:bucket], key: options[:key], upload_id: upload_id)
|
|
76
|
+
msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
|
|
89
77
|
raise MultipartUploadError.new(msg, errors)
|
|
90
|
-
rescue MultipartUploadError =>
|
|
91
|
-
raise
|
|
92
|
-
rescue =>
|
|
93
|
-
msg = "failed to abort multipart upload: #{
|
|
94
|
-
|
|
78
|
+
rescue MultipartUploadError => e
|
|
79
|
+
raise e
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
msg = "failed to abort multipart upload: #{e.message}. " \
|
|
82
|
+
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
|
|
83
|
+
raise MultipartUploadError.new(msg, errors + [e])
|
|
95
84
|
end
|
|
96
85
|
|
|
97
|
-
def compute_parts(upload_id, source, options)
|
|
98
|
-
|
|
99
|
-
default_part_size = compute_default_part_size(size)
|
|
86
|
+
def compute_parts(upload_id, source, file_size, options)
|
|
87
|
+
default_part_size = compute_default_part_size(file_size)
|
|
100
88
|
offset = 0
|
|
101
89
|
part_number = 1
|
|
102
90
|
parts = []
|
|
103
|
-
while offset <
|
|
91
|
+
while offset < file_size
|
|
104
92
|
parts << upload_part_opts(options).merge(
|
|
105
93
|
upload_id: upload_id,
|
|
106
94
|
part_number: part_number,
|
|
107
95
|
body: FilePart.new(
|
|
108
96
|
source: source,
|
|
109
97
|
offset: offset,
|
|
110
|
-
size: part_size(
|
|
98
|
+
size: part_size(file_size, default_part_size, offset)
|
|
111
99
|
)
|
|
112
100
|
)
|
|
113
101
|
part_number += 1
|
|
@@ -116,54 +104,79 @@ module Aws
|
|
|
116
104
|
parts
|
|
117
105
|
end
|
|
118
106
|
|
|
107
|
+
def checksum_key?(key)
|
|
108
|
+
CHECKSUM_KEYS.include?(key)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def has_checksum_key?(keys)
|
|
112
|
+
keys.any? { |key| checksum_key?(key) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def checksum_not_required?(options)
|
|
116
|
+
@client.config.request_checksum_calculation == 'when_required' && !options[:checksum_algorithm]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
119
|
def create_opts(options)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
opts = {}
|
|
121
|
+
unless checksum_not_required?(options)
|
|
122
|
+
opts[:checksum_algorithm] = Aws::Plugins::ChecksumAlgorithm::DEFAULT_CHECKSUM
|
|
123
123
|
end
|
|
124
|
+
opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
|
|
125
|
+
CREATE_OPTIONS.each_with_object(opts) { |k, h| h[k] = options[k] if options.key?(k) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def complete_opts(options)
|
|
129
|
+
opts = {}
|
|
130
|
+
opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
|
|
131
|
+
COMPLETE_OPTIONS.each_with_object(opts) { |k, h| h[k] = options[k] if options.key?(k) }
|
|
124
132
|
end
|
|
125
133
|
|
|
126
134
|
def upload_part_opts(options)
|
|
127
|
-
UPLOAD_PART_OPTIONS.
|
|
128
|
-
|
|
129
|
-
hash
|
|
135
|
+
UPLOAD_PART_OPTIONS.each_with_object({}) do |key, hash|
|
|
136
|
+
# don't pass through checksum calculations
|
|
137
|
+
hash[key] = options[key] if options.key?(key) && !checksum_key?(key)
|
|
130
138
|
end
|
|
131
139
|
end
|
|
132
140
|
|
|
133
|
-
def
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
error
|
|
157
|
-
end
|
|
141
|
+
def upload_with_executor(pending, completed, options)
|
|
142
|
+
upload_attempts = 0
|
|
143
|
+
completion_queue = Queue.new
|
|
144
|
+
abort_upload = false
|
|
145
|
+
errors = []
|
|
146
|
+
progress = MultipartProgress.new(pending, options[:progress_callback])
|
|
147
|
+
|
|
148
|
+
while (part = pending.shift)
|
|
149
|
+
break if abort_upload
|
|
150
|
+
|
|
151
|
+
upload_attempts += 1
|
|
152
|
+
@executor.post(part) do |p|
|
|
153
|
+
update_progress(progress, p)
|
|
154
|
+
resp = @client.upload_part(p)
|
|
155
|
+
p[:body].close
|
|
156
|
+
completed_part = { etag: resp.etag, part_number: p[:part_number] }
|
|
157
|
+
apply_part_checksum(resp, completed_part)
|
|
158
|
+
completed.push(completed_part)
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
abort_upload = true
|
|
161
|
+
errors << e
|
|
162
|
+
ensure
|
|
163
|
+
completion_queue << :done
|
|
158
164
|
end
|
|
159
|
-
thread.abort_on_exception = true
|
|
160
|
-
threads << thread
|
|
161
165
|
end
|
|
162
|
-
|
|
166
|
+
|
|
167
|
+
upload_attempts.times { completion_queue.pop }
|
|
168
|
+
errors
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def apply_part_checksum(resp, part)
|
|
172
|
+
return unless (checksum = resp.context.params[:checksum_algorithm])
|
|
173
|
+
|
|
174
|
+
k = :"checksum_#{checksum.downcase}"
|
|
175
|
+
part[k] = resp.send(k)
|
|
163
176
|
end
|
|
164
177
|
|
|
165
|
-
def compute_default_part_size(
|
|
166
|
-
[(
|
|
178
|
+
def compute_default_part_size(file_size)
|
|
179
|
+
[(file_size.to_f / MAX_PARTS).ceil, MIN_PART_SIZE].max.to_i
|
|
167
180
|
end
|
|
168
181
|
|
|
169
182
|
def part_size(total_size, part_size, offset)
|
|
@@ -174,9 +187,17 @@ module Aws
|
|
|
174
187
|
end
|
|
175
188
|
end
|
|
176
189
|
|
|
190
|
+
def update_progress(progress, part)
|
|
191
|
+
return unless progress.progress_callback
|
|
192
|
+
|
|
193
|
+
part[:on_chunk_sent] =
|
|
194
|
+
proc do |_chunk, bytes, _total|
|
|
195
|
+
progress.call(part[:part_number], bytes)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
177
199
|
# @api private
|
|
178
200
|
class PartList
|
|
179
|
-
|
|
180
201
|
def initialize(parts = [])
|
|
181
202
|
@parts = parts
|
|
182
203
|
@mutex = Mutex.new
|
|
@@ -205,7 +226,6 @@ module Aws
|
|
|
205
226
|
def to_a
|
|
206
227
|
@mutex.synchronize { @parts.dup }
|
|
207
228
|
end
|
|
208
|
-
|
|
209
229
|
end
|
|
210
230
|
|
|
211
231
|
# @api private
|
|
@@ -216,6 +236,8 @@ module Aws
|
|
|
216
236
|
@progress_callback = progress_callback
|
|
217
237
|
end
|
|
218
238
|
|
|
239
|
+
attr_reader :progress_callback
|
|
240
|
+
|
|
219
241
|
def call(part_number, bytes_read)
|
|
220
242
|
# part numbers start at 1
|
|
221
243
|
@bytes_sent[part_number - 1] = bytes_read
|
|
@@ -9,29 +9,18 @@ module Aws
|
|
|
9
9
|
module S3
|
|
10
10
|
# @api private
|
|
11
11
|
class MultipartStreamUploader
|
|
12
|
-
# api private
|
|
13
|
-
PART_SIZE = 5 * 1024 * 1024 # 5MB
|
|
14
12
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
TEMPFILE_PREIX = 'aws-sdk-s3-upload_stream'.freeze
|
|
20
|
-
|
|
21
|
-
# @api private
|
|
22
|
-
CREATE_OPTIONS =
|
|
23
|
-
Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
|
|
24
|
-
|
|
25
|
-
# @api private
|
|
26
|
-
UPLOAD_PART_OPTIONS =
|
|
27
|
-
Set.new(Client.api.operation(:upload_part).input.shape.member_names)
|
|
13
|
+
DEFAULT_PART_SIZE = 5 * 1024 * 1024 # 5MB
|
|
14
|
+
CREATE_OPTIONS = Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
|
|
15
|
+
UPLOAD_PART_OPTIONS = Set.new(Client.api.operation(:upload_part).input.shape.member_names)
|
|
16
|
+
COMPLETE_UPLOAD_OPTIONS = Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
|
|
28
17
|
|
|
29
18
|
# @option options [Client] :client
|
|
30
19
|
def initialize(options = {})
|
|
31
20
|
@client = options[:client] || Client.new
|
|
21
|
+
@executor = options[:executor]
|
|
32
22
|
@tempfile = options[:tempfile]
|
|
33
|
-
@part_size = options[:part_size] ||
|
|
34
|
-
@thread_count = options[:thread_count] || THREAD_COUNT
|
|
23
|
+
@part_size = options[:part_size] || DEFAULT_PART_SIZE
|
|
35
24
|
end
|
|
36
25
|
|
|
37
26
|
# @return [Client]
|
|
@@ -39,11 +28,13 @@ module Aws
|
|
|
39
28
|
|
|
40
29
|
# @option options [required,String] :bucket
|
|
41
30
|
# @option options [required,String] :key
|
|
42
|
-
# @return [
|
|
31
|
+
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
|
43
32
|
def upload(options = {}, &block)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
|
34
|
+
upload_id = initiate_upload(options)
|
|
35
|
+
parts = upload_parts(upload_id, options, &block)
|
|
36
|
+
complete_upload(upload_id, parts, options)
|
|
37
|
+
end
|
|
47
38
|
end
|
|
48
39
|
|
|
49
40
|
private
|
|
@@ -54,73 +45,80 @@ module Aws
|
|
|
54
45
|
|
|
55
46
|
def complete_upload(upload_id, parts, options)
|
|
56
47
|
@client.complete_multipart_upload(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
**complete_opts(options).merge(upload_id: upload_id, multipart_upload: { parts: parts })
|
|
49
|
+
)
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
abort_upload(upload_id, options, [e])
|
|
61
52
|
end
|
|
62
53
|
|
|
63
54
|
def upload_parts(upload_id, options, &block)
|
|
64
|
-
|
|
65
|
-
errors =
|
|
55
|
+
completed_parts = Queue.new
|
|
56
|
+
errors = []
|
|
57
|
+
|
|
58
|
+
begin
|
|
66
59
|
IO.pipe do |read_pipe, write_pipe|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
upload_thread = Thread.new do
|
|
61
|
+
upload_with_executor(
|
|
62
|
+
read_pipe,
|
|
63
|
+
completed_parts,
|
|
64
|
+
errors,
|
|
65
|
+
upload_part_opts(options).merge(upload_id: upload_id)
|
|
66
|
+
)
|
|
73
67
|
end
|
|
74
|
-
|
|
68
|
+
|
|
69
|
+
block.call(write_pipe)
|
|
70
|
+
ensure
|
|
71
|
+
# Ensure the pipe is closed to avoid https://github.com/jruby/jruby/issues/6111
|
|
72
|
+
write_pipe.close
|
|
73
|
+
upload_thread.join
|
|
75
74
|
end
|
|
76
|
-
rescue => e
|
|
77
|
-
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
errors << e
|
|
78
77
|
end
|
|
78
|
+
return ordered_parts(completed_parts) if errors.empty?
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
Array.new(completed.size) { completed.pop }.sort_by { |part| part[:part_number] }
|
|
82
|
-
else
|
|
83
|
-
abort_upload(upload_id, options, errors)
|
|
84
|
-
end
|
|
80
|
+
abort_upload(upload_id, options, errors)
|
|
85
81
|
end
|
|
86
82
|
|
|
87
83
|
def abort_upload(upload_id, options, errors)
|
|
88
|
-
@client.abort_multipart_upload(
|
|
89
|
-
|
|
90
|
-
key: options[:key],
|
|
91
|
-
upload_id: upload_id
|
|
92
|
-
)
|
|
93
|
-
msg = "multipart upload failed: #{errors.map(&:message).join("; ")}"
|
|
84
|
+
@client.abort_multipart_upload(bucket: options[:bucket], key: options[:key], upload_id: upload_id)
|
|
85
|
+
msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
|
|
94
86
|
raise MultipartUploadError.new(msg, errors)
|
|
95
|
-
rescue MultipartUploadError =>
|
|
96
|
-
raise
|
|
97
|
-
rescue =>
|
|
98
|
-
msg = "failed to abort multipart upload: #{
|
|
99
|
-
|
|
87
|
+
rescue MultipartUploadError => e
|
|
88
|
+
raise e
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
msg = "failed to abort multipart upload: #{e.message}. "\
|
|
91
|
+
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
|
|
92
|
+
raise MultipartUploadError.new(msg, errors + [e])
|
|
100
93
|
end
|
|
101
94
|
|
|
102
95
|
def create_opts(options)
|
|
103
|
-
CREATE_OPTIONS.
|
|
96
|
+
CREATE_OPTIONS.each_with_object({}) do |key, hash|
|
|
104
97
|
hash[key] = options[key] if options.key?(key)
|
|
105
|
-
hash
|
|
106
98
|
end
|
|
107
99
|
end
|
|
108
100
|
|
|
109
101
|
def upload_part_opts(options)
|
|
110
|
-
UPLOAD_PART_OPTIONS.
|
|
102
|
+
UPLOAD_PART_OPTIONS.each_with_object({}) do |key, hash|
|
|
103
|
+
hash[key] = options[key] if options.key?(key)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def complete_opts(options)
|
|
108
|
+
COMPLETE_UPLOAD_OPTIONS.each_with_object({}) do |key, hash|
|
|
111
109
|
hash[key] = options[key] if options.key?(key)
|
|
112
|
-
hash
|
|
113
110
|
end
|
|
114
111
|
end
|
|
115
112
|
|
|
116
113
|
def read_to_part_body(read_pipe)
|
|
117
114
|
return if read_pipe.closed?
|
|
118
|
-
|
|
115
|
+
|
|
116
|
+
temp_io = @tempfile ? Tempfile.new('aws-sdk-s3-upload_stream') : StringIO.new(String.new)
|
|
119
117
|
temp_io.binmode
|
|
120
118
|
bytes_copied = IO.copy_stream(read_pipe, temp_io, @part_size)
|
|
121
119
|
temp_io.rewind
|
|
122
|
-
if bytes_copied
|
|
123
|
-
if Tempfile
|
|
120
|
+
if bytes_copied.zero?
|
|
121
|
+
if temp_io.is_a?(Tempfile)
|
|
124
122
|
temp_io.close
|
|
125
123
|
temp_io.unlink
|
|
126
124
|
end
|
|
@@ -130,42 +128,62 @@ module Aws
|
|
|
130
128
|
end
|
|
131
129
|
end
|
|
132
130
|
|
|
133
|
-
def
|
|
134
|
-
|
|
131
|
+
def upload_with_executor(read_pipe, completed, errors, options)
|
|
132
|
+
completion_queue = Queue.new
|
|
133
|
+
queued_parts = 0
|
|
135
134
|
part_number = 0
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
body.close
|
|
154
|
-
body.unlink
|
|
155
|
-
elsif StringIO === body
|
|
156
|
-
body.string.clear
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
nil
|
|
161
|
-
rescue => error
|
|
162
|
-
# keep other threads from uploading other parts
|
|
163
|
-
mutex.synchronize { read_pipe.close_read unless read_pipe.closed? }
|
|
164
|
-
error
|
|
135
|
+
mutex = Mutex.new
|
|
136
|
+
loop do
|
|
137
|
+
part_body, current_part_num = mutex.synchronize do
|
|
138
|
+
[read_to_part_body(read_pipe), part_number += 1]
|
|
139
|
+
end
|
|
140
|
+
break unless part_body || current_part_num == 1
|
|
141
|
+
|
|
142
|
+
queued_parts += 1
|
|
143
|
+
@executor.post(part_body, current_part_num, options) do |body, num, opts|
|
|
144
|
+
part = opts.merge(body: body, part_number: num)
|
|
145
|
+
resp = @client.upload_part(part)
|
|
146
|
+
completed_part = create_completed_part(resp, part)
|
|
147
|
+
completed.push(completed_part)
|
|
148
|
+
rescue StandardError => e
|
|
149
|
+
mutex.synchronize do
|
|
150
|
+
errors.push(e)
|
|
151
|
+
read_pipe.close_read unless read_pipe.closed?
|
|
165
152
|
end
|
|
153
|
+
ensure
|
|
154
|
+
clear_body(body)
|
|
155
|
+
completion_queue << :done
|
|
166
156
|
end
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
end
|
|
158
|
+
queued_parts.times { completion_queue.pop }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def create_completed_part(resp, part)
|
|
162
|
+
completed_part = { etag: resp.etag, part_number: part[:part_number] }
|
|
163
|
+
return completed_part unless part[:checksum_algorithm]
|
|
164
|
+
|
|
165
|
+
# get the requested checksum from the response
|
|
166
|
+
k = "checksum_#{part[:checksum_algorithm].downcase}".to_sym
|
|
167
|
+
completed_part[k] = resp[k]
|
|
168
|
+
completed_part
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def ordered_parts(parts)
|
|
172
|
+
sorted = []
|
|
173
|
+
until parts.empty?
|
|
174
|
+
part = parts.pop
|
|
175
|
+
index = sorted.bsearch_index { |p| p[:part_number] >= part[:part_number] } || sorted.size
|
|
176
|
+
sorted.insert(index, part)
|
|
177
|
+
end
|
|
178
|
+
sorted
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def clear_body(body)
|
|
182
|
+
if body.is_a?(Tempfile)
|
|
183
|
+
body.close
|
|
184
|
+
body.unlink
|
|
185
|
+
elsif body.is_a?(StringIO)
|
|
186
|
+
body.string.clear
|
|
169
187
|
end
|
|
170
188
|
end
|
|
171
189
|
end
|