aws-sdk-s3 1.48.0 → 1.183.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/CHANGELOG.md +1352 -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 +1005 -106
- data/lib/aws-sdk-s3/bucket_acl.rb +65 -18
- data/lib/aws-sdk-s3/bucket_cors.rb +80 -18
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +71 -20
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +126 -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 +108 -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 +14517 -941
- data/lib/aws-sdk-s3/client_api.rb +1296 -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 +716 -0
- data/lib/aws-sdk-s3/endpoints.rb +1434 -0
- data/lib/aws-sdk-s3/errors.rb +170 -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 +161 -46
- 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 +104 -27
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +61 -21
- data/lib/aws-sdk-s3/multipart_upload.rb +342 -32
- data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
- data/lib/aws-sdk-s3/multipart_upload_part.rb +384 -46
- data/lib/aws-sdk-s3/object.rb +2600 -231
- 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 +2174 -204
- data/lib/aws-sdk-s3/object_version.rb +539 -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/checksum_algorithm.rb +31 -0
- 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 +88 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +62 -17
- 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 +14 -70
- 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/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 +141 -62
- data/lib/aws-sdk-s3/resource.rb +156 -17
- data/lib/aws-sdk-s3/types.rb +13021 -4106
- data/lib/aws-sdk-s3/waiters.rb +67 -1
- data/lib/aws-sdk-s3.rb +46 -32
- data/sig/bucket.rbs +222 -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 +2472 -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 +42 -0
- data/sig/multipart_upload.rbs +120 -0
- data/sig/multipart_upload_part.rbs +109 -0
- data/sig/object.rbs +459 -0
- data/sig/object_acl.rbs +86 -0
- data/sig/object_summary.rbs +345 -0
- data/sig/object_version.rbs +143 -0
- data/sig/resource.rbs +134 -0
- data/sig/types.rbs +2712 -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
|
@@ -15,15 +16,26 @@ module Aws
|
|
15
16
|
|
16
17
|
THREAD_COUNT = 10
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
CREATE_OPTIONS = Set.new(
|
20
|
+
Client.api.operation(:create_multipart_upload).input.shape.member_names
|
21
|
+
)
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
COMPLETE_OPTIONS = Set.new(
|
24
|
+
Client.api.operation(:complete_multipart_upload).input.shape.member_names
|
25
|
+
)
|
26
|
+
|
27
|
+
UPLOAD_PART_OPTIONS = Set.new(
|
28
|
+
Client.api.operation(:upload_part).input.shape.member_names
|
29
|
+
)
|
30
|
+
|
31
|
+
CHECKSUM_KEYS = Set.new(
|
32
|
+
Client.api.operation(:upload_part).input.shape.members.map do |n, s|
|
33
|
+
n if s.location == 'header' && s.location_name.start_with?('x-amz-checksum-')
|
34
|
+
end.compact
|
35
|
+
)
|
25
36
|
|
26
37
|
# @option options [Client] :client
|
38
|
+
# @option options [Integer] :thread_count (THREAD_COUNT)
|
27
39
|
def initialize(options = {})
|
28
40
|
@client = options[:client] || Client.new
|
29
41
|
@thread_count = options[:thread_count] || THREAD_COUNT
|
@@ -32,10 +44,13 @@ module Aws
|
|
32
44
|
# @return [Client]
|
33
45
|
attr_reader :client
|
34
46
|
|
35
|
-
# @param [String,Pathname,File,Tempfile] source
|
36
|
-
# @option options [required,String] :bucket
|
37
|
-
# @option options [required,String] :key
|
38
|
-
# @
|
47
|
+
# @param [String, Pathname, File, Tempfile] source The file to upload.
|
48
|
+
# @option options [required, String] :bucket The bucket to upload to.
|
49
|
+
# @option options [required, String] :key The key for the object.
|
50
|
+
# @option options [Proc] :progress_callback
|
51
|
+
# A Proc that will be called when each chunk of the upload is sent.
|
52
|
+
# It will be invoked with [bytes_read], [total_sizes]
|
53
|
+
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
39
54
|
def upload(source, options = {})
|
40
55
|
if File.size(source) < MIN_PART_SIZE
|
41
56
|
raise ArgumentError, FILE_TOO_SMALL
|
@@ -54,16 +69,17 @@ module Aws
|
|
54
69
|
|
55
70
|
def complete_upload(upload_id, parts, options)
|
56
71
|
@client.complete_multipart_upload(
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
72
|
+
**complete_opts(options).merge(
|
73
|
+
upload_id: upload_id,
|
74
|
+
multipart_upload: { parts: parts }
|
75
|
+
)
|
76
|
+
)
|
61
77
|
end
|
62
78
|
|
63
79
|
def upload_parts(upload_id, source, options)
|
64
80
|
pending = PartList.new(compute_parts(upload_id, source, options))
|
65
81
|
completed = PartList.new
|
66
|
-
errors = upload_in_threads(pending, completed)
|
82
|
+
errors = upload_in_threads(pending, completed, options)
|
67
83
|
if errors.empty?
|
68
84
|
completed.to_a.sort_by { |part| part[:part_number] }
|
69
85
|
else
|
@@ -77,12 +93,13 @@ module Aws
|
|
77
93
|
key: options[:key],
|
78
94
|
upload_id: upload_id
|
79
95
|
)
|
80
|
-
msg = "multipart upload failed: #{errors.map(&:message).join(
|
96
|
+
msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
|
81
97
|
raise MultipartUploadError.new(msg, errors)
|
82
98
|
rescue MultipartUploadError => error
|
83
99
|
raise error
|
84
100
|
rescue => error
|
85
|
-
msg = "failed to abort multipart upload: #{error.message}"
|
101
|
+
msg = "failed to abort multipart upload: #{error.message}. "\
|
102
|
+
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
|
86
103
|
raise MultipartUploadError.new(msg, errors + [error])
|
87
104
|
end
|
88
105
|
|
@@ -93,7 +110,7 @@ module Aws
|
|
93
110
|
part_number = 1
|
94
111
|
parts = []
|
95
112
|
while offset < size
|
96
|
-
parts << upload_part_opts(options).merge(
|
113
|
+
parts << upload_part_opts(options).merge(
|
97
114
|
upload_id: upload_id,
|
98
115
|
part_number: part_number,
|
99
116
|
body: FilePart.new(
|
@@ -101,15 +118,34 @@ module Aws
|
|
101
118
|
offset: offset,
|
102
119
|
size: part_size(size, default_part_size, offset)
|
103
120
|
)
|
104
|
-
|
121
|
+
)
|
105
122
|
part_number += 1
|
106
123
|
offset += default_part_size
|
107
124
|
end
|
108
125
|
parts
|
109
126
|
end
|
110
127
|
|
128
|
+
def checksum_key?(key)
|
129
|
+
CHECKSUM_KEYS.include?(key)
|
130
|
+
end
|
131
|
+
|
132
|
+
def has_checksum_key?(keys)
|
133
|
+
keys.any? { |key| checksum_key?(key) }
|
134
|
+
end
|
135
|
+
|
111
136
|
def create_opts(options)
|
112
|
-
|
137
|
+
opts = { checksum_algorithm: Aws::Plugins::ChecksumAlgorithm::DEFAULT_CHECKSUM }
|
138
|
+
opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
|
139
|
+
CREATE_OPTIONS.inject(opts) do |hash, key|
|
140
|
+
hash[key] = options[key] if options.key?(key)
|
141
|
+
hash
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def complete_opts(options)
|
146
|
+
opts = {}
|
147
|
+
opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
|
148
|
+
COMPLETE_OPTIONS.inject(opts) do |hash, key|
|
113
149
|
hash[key] = options[key] if options.key?(key)
|
114
150
|
hash
|
115
151
|
end
|
@@ -117,20 +153,39 @@ module Aws
|
|
117
153
|
|
118
154
|
def upload_part_opts(options)
|
119
155
|
UPLOAD_PART_OPTIONS.inject({}) do |hash, key|
|
120
|
-
|
156
|
+
if options.key?(key)
|
157
|
+
# don't pass through checksum calculations
|
158
|
+
hash[key] = options[key] unless checksum_key?(key)
|
159
|
+
end
|
121
160
|
hash
|
122
161
|
end
|
123
162
|
end
|
124
163
|
|
125
|
-
def upload_in_threads(pending, completed)
|
164
|
+
def upload_in_threads(pending, completed, options)
|
126
165
|
threads = []
|
127
|
-
|
166
|
+
if (callback = options[:progress_callback])
|
167
|
+
progress = MultipartProgress.new(pending, callback)
|
168
|
+
end
|
169
|
+
options.fetch(:thread_count, @thread_count).times do
|
128
170
|
thread = Thread.new do
|
129
171
|
begin
|
130
172
|
while part = pending.shift
|
173
|
+
if progress
|
174
|
+
part[:on_chunk_sent] =
|
175
|
+
proc do |_chunk, bytes, _total|
|
176
|
+
progress.call(part[:part_number], bytes)
|
177
|
+
end
|
178
|
+
end
|
131
179
|
resp = @client.upload_part(part)
|
132
180
|
part[:body].close
|
133
|
-
|
181
|
+
completed_part = {
|
182
|
+
etag: resp.etag,
|
183
|
+
part_number: part[:part_number]
|
184
|
+
}
|
185
|
+
algorithm = resp.context.params[:checksum_algorithm]
|
186
|
+
k = "checksum_#{algorithm.downcase}".to_sym
|
187
|
+
completed_part[k] = resp.send(k)
|
188
|
+
completed.push(completed_part)
|
134
189
|
end
|
135
190
|
nil
|
136
191
|
rescue => error
|
@@ -139,7 +194,6 @@ module Aws
|
|
139
194
|
error
|
140
195
|
end
|
141
196
|
end
|
142
|
-
thread.abort_on_exception = true
|
143
197
|
threads << thread
|
144
198
|
end
|
145
199
|
threads.map(&:value).compact
|
@@ -177,11 +231,34 @@ module Aws
|
|
177
231
|
@mutex.synchronize { @parts.clear }
|
178
232
|
end
|
179
233
|
|
234
|
+
def size
|
235
|
+
@mutex.synchronize { @parts.size }
|
236
|
+
end
|
237
|
+
|
238
|
+
def part_sizes
|
239
|
+
@mutex.synchronize { @parts.map { |p| p[:body].size } }
|
240
|
+
end
|
241
|
+
|
180
242
|
def to_a
|
181
243
|
@mutex.synchronize { @parts.dup }
|
182
244
|
end
|
183
245
|
|
184
246
|
end
|
247
|
+
|
248
|
+
# @api private
|
249
|
+
class MultipartProgress
|
250
|
+
def initialize(parts, progress_callback)
|
251
|
+
@bytes_sent = Array.new(parts.size, 0)
|
252
|
+
@total_sizes = parts.part_sizes
|
253
|
+
@progress_callback = progress_callback
|
254
|
+
end
|
255
|
+
|
256
|
+
def call(part_number, bytes_read)
|
257
|
+
# part numbers start at 1
|
258
|
+
@bytes_sent[part_number - 1] = bytes_read
|
259
|
+
@progress_callback.call(@bytes_sent, @total_sizes)
|
260
|
+
end
|
261
|
+
end
|
185
262
|
end
|
186
263
|
end
|
187
|
-
end
|
264
|
+
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
|