aws-sdk-s3 1.48.0 → 1.169.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1270 -0
- data/LICENSE.txt +202 -0
- data/VERSION +1 -0
- 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 +959 -106
- data/lib/aws-sdk-s3/bucket_acl.rb +64 -18
- data/lib/aws-sdk-s3/bucket_cors.rb +79 -18
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +66 -20
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +106 -21
- data/lib/aws-sdk-s3/bucket_logging.rb +68 -16
- data/lib/aws-sdk-s3/bucket_notification.rb +52 -20
- data/lib/aws-sdk-s3/bucket_policy.rb +107 -17
- data/lib/aws-sdk-s3/bucket_region_cache.rb +11 -5
- data/lib/aws-sdk-s3/bucket_request_payment.rb +60 -15
- data/lib/aws-sdk-s3/bucket_tagging.rb +71 -18
- data/lib/aws-sdk-s3/bucket_versioning.rb +133 -17
- data/lib/aws-sdk-s3/bucket_website.rb +78 -21
- data/lib/aws-sdk-s3/client.rb +13765 -1019
- data/lib/aws-sdk-s3/client_api.rb +1137 -197
- data/lib/aws-sdk-s3/customizations/bucket.rb +56 -37
- data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
- data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
- data/lib/aws-sdk-s3/customizations/object.rb +288 -68
- data/lib/aws-sdk-s3/customizations/object_summary.rb +10 -0
- data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
- data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
- data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
- data/lib/aws-sdk-s3/customizations.rb +27 -28
- data/lib/aws-sdk-s3/encryption/client.rb +28 -7
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +43 -5
- data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +13 -2
- data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
- data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
- data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +46 -11
- data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
- data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
- data/lib/aws-sdk-s3/encryption.rb +4 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +570 -0
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +223 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +170 -0
- data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +65 -0
- data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
- data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +73 -0
- data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +173 -0
- data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +103 -0
- data/lib/aws-sdk-s3/encryption_v2.rb +23 -0
- data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
- data/lib/aws-sdk-s3/endpoint_provider.rb +592 -0
- data/lib/aws-sdk-s3/endpoints.rb +1392 -0
- data/lib/aws-sdk-s3/errors.rb +126 -1
- data/lib/aws-sdk-s3/event_streams.rb +8 -1
- 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 +176 -44
- data/lib/aws-sdk-s3/file_part.rb +11 -6
- data/lib/aws-sdk-s3/file_uploader.rb +39 -18
- data/lib/aws-sdk-s3/legacy_signer.rb +17 -25
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +82 -23
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +61 -21
- data/lib/aws-sdk-s3/multipart_upload.rb +265 -32
- data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
- data/lib/aws-sdk-s3/multipart_upload_part.rb +367 -45
- data/lib/aws-sdk-s3/object.rb +2475 -228
- data/lib/aws-sdk-s3/object_acl.rb +103 -25
- data/lib/aws-sdk-s3/object_copier.rb +9 -5
- data/lib/aws-sdk-s3/object_multipart_copier.rb +48 -22
- data/lib/aws-sdk-s3/object_summary.rb +2075 -203
- data/lib/aws-sdk-s3/object_version.rb +492 -80
- data/lib/aws-sdk-s3/plugins/accelerate.rb +17 -64
- data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
- data/lib/aws-sdk-s3/plugins/arn.rb +70 -0
- data/lib/aws-sdk-s3/plugins/bucket_dns.rb +7 -43
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +20 -3
- data/lib/aws-sdk-s3/plugins/dualstack.rb +7 -50
- data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +5 -4
- data/lib/aws-sdk-s3/plugins/express_session_auth.rb +97 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +60 -15
- data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +44 -0
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +5 -1
- data/lib/aws-sdk-s3/plugins/md5s.rb +35 -30
- data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
- data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +63 -94
- data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +31 -0
- data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +139 -0
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
- data/lib/aws-sdk-s3/presigned_post.rb +160 -99
- data/lib/aws-sdk-s3/presigner.rb +138 -59
- data/lib/aws-sdk-s3/resource.rb +155 -17
- data/lib/aws-sdk-s3/types.rb +12229 -4377
- data/lib/aws-sdk-s3/waiters.rb +67 -1
- data/lib/aws-sdk-s3.rb +46 -32
- data/sig/bucket.rbs +216 -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 +2406 -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 +34 -0
- data/sig/multipart_upload.rbs +111 -0
- data/sig/multipart_upload_part.rbs +105 -0
- data/sig/object.rbs +443 -0
- data/sig/object_acl.rbs +86 -0
- data/sig/object_summary.rbs +335 -0
- data/sig/object_version.rbs +137 -0
- data/sig/resource.rbs +132 -0
- data/sig/types.rbs +2596 -0
- data/sig/waiters.rbs +95 -0
- metadata +74 -15
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pathname'
|
2
4
|
|
3
5
|
module Aws
|
@@ -5,44 +7,49 @@ module Aws
|
|
5
7
|
# @api private
|
6
8
|
class FileUploader
|
7
9
|
|
8
|
-
|
10
|
+
ONE_HUNDRED_MEGABYTES = 100 * 1024 * 1024
|
9
11
|
|
12
|
+
# @param [Hash] options
|
10
13
|
# @option options [Client] :client
|
11
|
-
# @option options [Integer] :multipart_threshold
|
12
|
-
# `:multipart_threshold` bytes are uploaded using S3 multipart APIs.
|
14
|
+
# @option options [Integer] :multipart_threshold (104857600)
|
13
15
|
def initialize(options = {})
|
14
16
|
@options = options
|
15
17
|
@client = options[:client] || Client.new
|
16
|
-
@multipart_threshold = options[:multipart_threshold] ||
|
18
|
+
@multipart_threshold = options[:multipart_threshold] ||
|
19
|
+
ONE_HUNDRED_MEGABYTES
|
17
20
|
end
|
18
21
|
|
19
22
|
# @return [Client]
|
20
23
|
attr_reader :client
|
21
24
|
|
22
|
-
# @return [Integer] Files larger than this in bytes are uploaded
|
25
|
+
# @return [Integer] Files larger than or equal to this in bytes are uploaded
|
23
26
|
# using a {MultipartFileUploader}.
|
24
27
|
attr_reader :multipart_threshold
|
25
28
|
|
26
|
-
# @param [String,Pathname,File,Tempfile] source
|
27
|
-
# @option options [required,String] :bucket
|
28
|
-
# @option options [required,String] :key
|
29
|
+
# @param [String, Pathname, File, Tempfile] source The file to upload.
|
30
|
+
# @option options [required, String] :bucket The bucket to upload to.
|
31
|
+
# @option options [required, String] :key The key for the object.
|
32
|
+
# @option options [Proc] :progress_callback
|
33
|
+
# A Proc that will be called when each chunk of the upload is sent.
|
34
|
+
# It will be invoked with [bytes_read], [total_sizes]
|
35
|
+
# @option options [Integer] :thread_count
|
36
|
+
# The thread count to use for multipart uploads. Ignored for
|
37
|
+
# objects smaller than the multipart threshold.
|
29
38
|
# @return [void]
|
30
39
|
def upload(source, options = {})
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
41
|
+
if File.size(source) >= multipart_threshold
|
42
|
+
MultipartFileUploader.new(@options).upload(source, options)
|
43
|
+
else
|
44
|
+
# remove multipart parameters not supported by put_object
|
45
|
+
options.delete(:thread_count)
|
46
|
+
put_object(source, options)
|
47
|
+
end
|
35
48
|
end
|
36
49
|
end
|
37
50
|
|
38
51
|
private
|
39
52
|
|
40
|
-
def put_object(source, options)
|
41
|
-
open_file(source) do |file|
|
42
|
-
@client.put_object(options.merge(body: file))
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
53
|
def open_file(source)
|
47
54
|
if String === source || Pathname === source
|
48
55
|
File.open(source, 'rb') { |file| yield(file) }
|
@@ -51,6 +58,20 @@ module Aws
|
|
51
58
|
end
|
52
59
|
end
|
53
60
|
|
61
|
+
def put_object(source, options)
|
62
|
+
if (callback = options.delete(:progress_callback))
|
63
|
+
options[:on_chunk_sent] = single_part_progress(callback)
|
64
|
+
end
|
65
|
+
open_file(source) do |file|
|
66
|
+
@client.put_object(options.merge(body: file))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def single_part_progress(progress_callback)
|
71
|
+
proc do |_chunk, bytes_read, total_size|
|
72
|
+
progress_callback.call([bytes_read], [total_size])
|
73
|
+
end
|
74
|
+
end
|
54
75
|
end
|
55
76
|
end
|
56
77
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
require 'time'
|
3
5
|
require 'openssl'
|
4
6
|
require 'cgi'
|
5
|
-
require 'webrick/httputils'
|
6
7
|
require 'aws-sdk-core/query'
|
7
8
|
|
8
9
|
module Aws
|
@@ -155,33 +156,24 @@ module Aws
|
|
155
156
|
end
|
156
157
|
|
157
158
|
def uri_escape(s)
|
158
|
-
|
159
159
|
#URI.escape(s)
|
160
160
|
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
# (
|
169
|
-
#
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
175
|
-
|
176
|
-
# URI.escape(s),
|
177
|
-
# ]
|
178
|
-
# next if e.uniq.length == 1
|
179
|
-
# puts("%5s %5s %5s %5s %5s %5s %5s" % ([s.inspect] + e))
|
180
|
-
# }
|
181
|
-
#
|
182
|
-
WEBrick::HTTPUtils.escape(s).gsub('%5B', '[').gsub('%5D', ']')
|
161
|
+
# (0..255).each {|c|
|
162
|
+
# s = [c].pack("C")
|
163
|
+
# e = [
|
164
|
+
# CGI.escape(s),
|
165
|
+
# ERB::Util.url_encode(s),
|
166
|
+
# URI.encode_www_form_component(s),
|
167
|
+
# WEBrick::HTTPUtils.escape_form(s),
|
168
|
+
# WEBrick::HTTPUtils.escape(s),
|
169
|
+
# URI.escape(s),
|
170
|
+
# URI::DEFAULT_PARSER.escape(s)
|
171
|
+
# ]
|
172
|
+
# next if e.uniq.length == 1
|
173
|
+
# puts("%5s %5s %5s %5s %5s %5s %5s %5s" % ([s.inspect] + e))
|
174
|
+
# }
|
175
|
+
URI::DEFAULT_PARSER.escape(s)
|
183
176
|
end
|
184
|
-
|
185
177
|
end
|
186
178
|
end
|
187
179
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pathname'
|
2
|
-
require 'thread'
|
3
4
|
require 'set'
|
4
5
|
|
5
6
|
module Aws
|
@@ -16,14 +17,21 @@ module Aws
|
|
16
17
|
THREAD_COUNT = 10
|
17
18
|
|
18
19
|
# @api private
|
19
|
-
CREATE_OPTIONS =
|
20
|
-
|
20
|
+
CREATE_OPTIONS = Set.new(
|
21
|
+
Client.api.operation(:create_multipart_upload).input.shape.member_names
|
22
|
+
)
|
23
|
+
|
24
|
+
COMPLETE_OPTIONS = Set.new(
|
25
|
+
Client.api.operation(:complete_multipart_upload).input.shape.member_names
|
26
|
+
)
|
21
27
|
|
22
28
|
# @api private
|
23
|
-
UPLOAD_PART_OPTIONS =
|
24
|
-
|
29
|
+
UPLOAD_PART_OPTIONS = Set.new(
|
30
|
+
Client.api.operation(:upload_part).input.shape.member_names
|
31
|
+
)
|
25
32
|
|
26
33
|
# @option options [Client] :client
|
34
|
+
# @option options [Integer] :thread_count (THREAD_COUNT)
|
27
35
|
def initialize(options = {})
|
28
36
|
@client = options[:client] || Client.new
|
29
37
|
@thread_count = options[:thread_count] || THREAD_COUNT
|
@@ -32,10 +40,13 @@ module Aws
|
|
32
40
|
# @return [Client]
|
33
41
|
attr_reader :client
|
34
42
|
|
35
|
-
# @param [String,Pathname,File,Tempfile] source
|
36
|
-
# @option options [required,String] :bucket
|
37
|
-
# @option options [required,String] :key
|
38
|
-
# @
|
43
|
+
# @param [String, Pathname, File, Tempfile] source The file to upload.
|
44
|
+
# @option options [required, String] :bucket The bucket to upload to.
|
45
|
+
# @option options [required, String] :key The key for the object.
|
46
|
+
# @option options [Proc] :progress_callback
|
47
|
+
# A Proc that will be called when each chunk of the upload is sent.
|
48
|
+
# It will be invoked with [bytes_read], [total_sizes]
|
49
|
+
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
39
50
|
def upload(source, options = {})
|
40
51
|
if File.size(source) < MIN_PART_SIZE
|
41
52
|
raise ArgumentError, FILE_TOO_SMALL
|
@@ -54,16 +65,17 @@ module Aws
|
|
54
65
|
|
55
66
|
def complete_upload(upload_id, parts, options)
|
56
67
|
@client.complete_multipart_upload(
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
68
|
+
**complete_opts(options).merge(
|
69
|
+
upload_id: upload_id,
|
70
|
+
multipart_upload: { parts: parts }
|
71
|
+
)
|
72
|
+
)
|
61
73
|
end
|
62
74
|
|
63
75
|
def upload_parts(upload_id, source, options)
|
64
76
|
pending = PartList.new(compute_parts(upload_id, source, options))
|
65
77
|
completed = PartList.new
|
66
|
-
errors = upload_in_threads(pending, completed)
|
78
|
+
errors = upload_in_threads(pending, completed, options)
|
67
79
|
if errors.empty?
|
68
80
|
completed.to_a.sort_by { |part| part[:part_number] }
|
69
81
|
else
|
@@ -77,12 +89,13 @@ module Aws
|
|
77
89
|
key: options[:key],
|
78
90
|
upload_id: upload_id
|
79
91
|
)
|
80
|
-
msg = "multipart upload failed: #{errors.map(&:message).join(
|
92
|
+
msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
|
81
93
|
raise MultipartUploadError.new(msg, errors)
|
82
94
|
rescue MultipartUploadError => error
|
83
95
|
raise error
|
84
96
|
rescue => error
|
85
|
-
msg = "failed to abort multipart upload: #{error.message}"
|
97
|
+
msg = "failed to abort multipart upload: #{error.message}. "\
|
98
|
+
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
|
86
99
|
raise MultipartUploadError.new(msg, errors + [error])
|
87
100
|
end
|
88
101
|
|
@@ -93,7 +106,7 @@ module Aws
|
|
93
106
|
part_number = 1
|
94
107
|
parts = []
|
95
108
|
while offset < size
|
96
|
-
parts << upload_part_opts(options).merge(
|
109
|
+
parts << upload_part_opts(options).merge(
|
97
110
|
upload_id: upload_id,
|
98
111
|
part_number: part_number,
|
99
112
|
body: FilePart.new(
|
@@ -101,7 +114,7 @@ module Aws
|
|
101
114
|
offset: offset,
|
102
115
|
size: part_size(size, default_part_size, offset)
|
103
116
|
)
|
104
|
-
|
117
|
+
)
|
105
118
|
part_number += 1
|
106
119
|
offset += default_part_size
|
107
120
|
end
|
@@ -115,6 +128,13 @@ module Aws
|
|
115
128
|
end
|
116
129
|
end
|
117
130
|
|
131
|
+
def complete_opts(options)
|
132
|
+
COMPLETE_OPTIONS.inject({}) do |hash, key|
|
133
|
+
hash[key] = options[key] if options.key?(key)
|
134
|
+
hash
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
118
138
|
def upload_part_opts(options)
|
119
139
|
UPLOAD_PART_OPTIONS.inject({}) do |hash, key|
|
120
140
|
hash[key] = options[key] if options.key?(key)
|
@@ -122,15 +142,32 @@ module Aws
|
|
122
142
|
end
|
123
143
|
end
|
124
144
|
|
125
|
-
def upload_in_threads(pending, completed)
|
145
|
+
def upload_in_threads(pending, completed, options)
|
126
146
|
threads = []
|
127
|
-
|
147
|
+
if (callback = options[:progress_callback])
|
148
|
+
progress = MultipartProgress.new(pending, callback)
|
149
|
+
end
|
150
|
+
options.fetch(:thread_count, @thread_count).times do
|
128
151
|
thread = Thread.new do
|
129
152
|
begin
|
130
153
|
while part = pending.shift
|
154
|
+
if progress
|
155
|
+
part[:on_chunk_sent] =
|
156
|
+
proc do |_chunk, bytes, _total|
|
157
|
+
progress.call(part[:part_number], bytes)
|
158
|
+
end
|
159
|
+
end
|
131
160
|
resp = @client.upload_part(part)
|
132
161
|
part[:body].close
|
133
|
-
|
162
|
+
completed_part = {etag: resp.etag, part_number: part[:part_number]}
|
163
|
+
|
164
|
+
# get the requested checksum from the response
|
165
|
+
if part[:checksum_algorithm]
|
166
|
+
k = "checksum_#{part[:checksum_algorithm].downcase}".to_sym
|
167
|
+
completed_part[k] = resp[k]
|
168
|
+
end
|
169
|
+
|
170
|
+
completed.push(completed_part)
|
134
171
|
end
|
135
172
|
nil
|
136
173
|
rescue => error
|
@@ -139,7 +176,6 @@ module Aws
|
|
139
176
|
error
|
140
177
|
end
|
141
178
|
end
|
142
|
-
thread.abort_on_exception = true
|
143
179
|
threads << thread
|
144
180
|
end
|
145
181
|
threads.map(&:value).compact
|
@@ -177,11 +213,34 @@ module Aws
|
|
177
213
|
@mutex.synchronize { @parts.clear }
|
178
214
|
end
|
179
215
|
|
216
|
+
def size
|
217
|
+
@mutex.synchronize { @parts.size }
|
218
|
+
end
|
219
|
+
|
220
|
+
def part_sizes
|
221
|
+
@mutex.synchronize { @parts.map { |p| p[:body].size } }
|
222
|
+
end
|
223
|
+
|
180
224
|
def to_a
|
181
225
|
@mutex.synchronize { @parts.dup }
|
182
226
|
end
|
183
227
|
|
184
228
|
end
|
229
|
+
|
230
|
+
# @api private
|
231
|
+
class MultipartProgress
|
232
|
+
def initialize(parts, progress_callback)
|
233
|
+
@bytes_sent = Array.new(parts.size, 0)
|
234
|
+
@total_sizes = parts.part_sizes
|
235
|
+
@progress_callback = progress_callback
|
236
|
+
end
|
237
|
+
|
238
|
+
def call(part_number, bytes_read)
|
239
|
+
# part numbers start at 1
|
240
|
+
@bytes_sent[part_number - 1] = bytes_read
|
241
|
+
@progress_callback.call(@bytes_sent, @total_sizes)
|
242
|
+
end
|
243
|
+
end
|
185
244
|
end
|
186
245
|
end
|
187
|
-
end
|
246
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thread'
|
2
4
|
require 'set'
|
3
5
|
require 'tempfile'
|
@@ -24,6 +26,10 @@ module Aws
|
|
24
26
|
UPLOAD_PART_OPTIONS =
|
25
27
|
Set.new(Client.api.operation(:upload_part).input.shape.member_names)
|
26
28
|
|
29
|
+
# @api private
|
30
|
+
COMPLETE_UPLOAD_OPTIONS =
|
31
|
+
Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
|
32
|
+
|
27
33
|
# @option options [Client] :client
|
28
34
|
def initialize(options = {})
|
29
35
|
@client = options[:client] || Client.new
|
@@ -37,11 +43,14 @@ module Aws
|
|
37
43
|
|
38
44
|
# @option options [required,String] :bucket
|
39
45
|
# @option options [required,String] :key
|
40
|
-
# @
|
46
|
+
# @option options [Integer] :thread_count (THREAD_COUNT)
|
47
|
+
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
41
48
|
def upload(options = {}, &block)
|
42
|
-
|
43
|
-
|
44
|
-
|
49
|
+
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
50
|
+
upload_id = initiate_upload(options)
|
51
|
+
parts = upload_parts(upload_id, options, &block)
|
52
|
+
complete_upload(upload_id, parts, options)
|
53
|
+
end
|
45
54
|
end
|
46
55
|
|
47
56
|
private
|
@@ -52,20 +61,34 @@ module Aws
|
|
52
61
|
|
53
62
|
def complete_upload(upload_id, parts, options)
|
54
63
|
@client.complete_multipart_upload(
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
64
|
+
**complete_opts(options).merge(
|
65
|
+
upload_id: upload_id,
|
66
|
+
multipart_upload: { parts: parts }
|
67
|
+
)
|
68
|
+
)
|
59
69
|
end
|
60
70
|
|
61
71
|
def upload_parts(upload_id, options, &block)
|
62
72
|
completed = Queue.new
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
73
|
+
thread_errors = []
|
74
|
+
errors = begin
|
75
|
+
IO.pipe do |read_pipe, write_pipe|
|
76
|
+
threads = upload_in_threads(
|
77
|
+
read_pipe, completed,
|
78
|
+
upload_part_opts(options).merge(upload_id: upload_id),
|
79
|
+
thread_errors)
|
80
|
+
begin
|
81
|
+
block.call(write_pipe)
|
82
|
+
ensure
|
83
|
+
# Ensure the pipe is closed to avoid https://github.com/jruby/jruby/issues/6111
|
84
|
+
write_pipe.close
|
85
|
+
end
|
86
|
+
threads.map(&:value).compact
|
87
|
+
end
|
88
|
+
rescue => e
|
89
|
+
thread_errors + [e]
|
68
90
|
end
|
91
|
+
|
69
92
|
if errors.empty?
|
70
93
|
Array.new(completed.size) { completed.pop }.sort_by { |part| part[:part_number] }
|
71
94
|
else
|
@@ -79,12 +102,13 @@ module Aws
|
|
79
102
|
key: options[:key],
|
80
103
|
upload_id: upload_id
|
81
104
|
)
|
82
|
-
msg = "multipart upload failed: #{errors.map(&:message).join(
|
105
|
+
msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
|
83
106
|
raise MultipartUploadError.new(msg, errors)
|
84
107
|
rescue MultipartUploadError => error
|
85
108
|
raise error
|
86
109
|
rescue => error
|
87
|
-
msg = "failed to abort multipart upload: #{error.message}"
|
110
|
+
msg = "failed to abort multipart upload: #{error.message}. "\
|
111
|
+
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
|
88
112
|
raise MultipartUploadError.new(msg, errors + [error])
|
89
113
|
end
|
90
114
|
|
@@ -102,9 +126,16 @@ module Aws
|
|
102
126
|
end
|
103
127
|
end
|
104
128
|
|
129
|
+
def complete_opts(options)
|
130
|
+
COMPLETE_UPLOAD_OPTIONS.inject({}) do |hash, key|
|
131
|
+
hash[key] = options[key] if options.key?(key)
|
132
|
+
hash
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
105
136
|
def read_to_part_body(read_pipe)
|
106
137
|
return if read_pipe.closed?
|
107
|
-
temp_io = @tempfile ? Tempfile.new(TEMPFILE_PREIX) : StringIO.new
|
138
|
+
temp_io = @tempfile ? Tempfile.new(TEMPFILE_PREIX) : StringIO.new(String.new)
|
108
139
|
temp_io.binmode
|
109
140
|
bytes_copied = IO.copy_stream(read_pipe, temp_io, @part_size)
|
110
141
|
temp_io.rewind
|
@@ -119,10 +150,10 @@ module Aws
|
|
119
150
|
end
|
120
151
|
end
|
121
152
|
|
122
|
-
def upload_in_threads(read_pipe, completed, options)
|
153
|
+
def upload_in_threads(read_pipe, completed, options, thread_errors)
|
123
154
|
mutex = Mutex.new
|
124
155
|
part_number = 0
|
125
|
-
@thread_count.times.map do
|
156
|
+
options.fetch(:thread_count, @thread_count).times.map do
|
126
157
|
thread = Thread.new do
|
127
158
|
begin
|
128
159
|
loop do
|
@@ -136,7 +167,14 @@ module Aws
|
|
136
167
|
part_number: thread_part_number,
|
137
168
|
)
|
138
169
|
resp = @client.upload_part(part)
|
139
|
-
|
170
|
+
completed_part = {etag: resp.etag, part_number: part[:part_number]}
|
171
|
+
|
172
|
+
# get the requested checksum from the response
|
173
|
+
if part[:checksum_algorithm]
|
174
|
+
k = "checksum_#{part[:checksum_algorithm].downcase}".to_sym
|
175
|
+
completed_part[k] = resp[k]
|
176
|
+
end
|
177
|
+
completed.push(completed_part)
|
140
178
|
ensure
|
141
179
|
if Tempfile === body
|
142
180
|
body.close
|
@@ -149,11 +187,13 @@ module Aws
|
|
149
187
|
nil
|
150
188
|
rescue => error
|
151
189
|
# keep other threads from uploading other parts
|
152
|
-
mutex.synchronize
|
190
|
+
mutex.synchronize do
|
191
|
+
thread_errors.push(error)
|
192
|
+
read_pipe.close_read unless read_pipe.closed?
|
193
|
+
end
|
153
194
|
error
|
154
195
|
end
|
155
196
|
end
|
156
|
-
thread.abort_on_exception = true
|
157
197
|
thread
|
158
198
|
end
|
159
199
|
end
|