aws-sdk-s3 1.136.0 → 1.208.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 +452 -1
- 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 +728 -145
- data/lib/aws-sdk-s3/bucket_acl.rb +19 -18
- data/lib/aws-sdk-s3/bucket_cors.rb +22 -21
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +23 -18
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +76 -19
- data/lib/aws-sdk-s3/bucket_logging.rb +21 -14
- data/lib/aws-sdk-s3/bucket_notification.rb +6 -6
- data/lib/aws-sdk-s3/bucket_policy.rb +65 -20
- data/lib/aws-sdk-s3/bucket_region_cache.rb +9 -5
- data/lib/aws-sdk-s3/bucket_request_payment.rb +15 -15
- data/lib/aws-sdk-s3/bucket_tagging.rb +19 -19
- data/lib/aws-sdk-s3/bucket_versioning.rb +74 -41
- data/lib/aws-sdk-s3/bucket_website.rb +19 -19
- data/lib/aws-sdk-s3/client.rb +9951 -3365
- data/lib/aws-sdk-s3/client_api.rb +758 -164
- data/lib/aws-sdk-s3/customizations/bucket.rb +1 -1
- data/lib/aws-sdk-s3/customizations/errors.rb +15 -2
- data/lib/aws-sdk-s3/customizations/object.rb +87 -91
- 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.rb +28 -31
- data/lib/aws-sdk-s3/default_executor.rb +103 -0
- data/lib/aws-sdk-s3/encryption/client.rb +4 -4
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +4 -2
- data/lib/aws-sdk-s3/encryptionV2/client.rb +100 -25
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -162
- data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +17 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +10 -2
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
- data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
- data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
- data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
- data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
- data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
- data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
- data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
- data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
- data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
- data/lib/aws-sdk-s3/endpoint_parameters.rb +65 -30
- data/lib/aws-sdk-s3/endpoint_provider.rb +604 -227
- data/lib/aws-sdk-s3/endpoints.rb +655 -1261
- 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 +192 -147
- data/lib/aws-sdk-s3/file_uploader.rb +10 -14
- 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 +106 -103
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +99 -109
- data/lib/aws-sdk-s3/multipart_upload.rb +185 -38
- data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
- data/lib/aws-sdk-s3/multipart_upload_part.rb +205 -68
- data/lib/aws-sdk-s3/object.rb +2052 -315
- data/lib/aws-sdk-s3/object_acl.rb +46 -28
- data/lib/aws-sdk-s3/object_copier.rb +1 -1
- data/lib/aws-sdk-s3/object_multipart_copier.rb +12 -9
- data/lib/aws-sdk-s3/object_summary.rb +1778 -262
- data/lib/aws-sdk-s3/object_version.rb +400 -68
- data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
- data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
- data/lib/aws-sdk-s3/plugins/endpoints.rb +32 -208
- 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/location_constraint.rb +3 -1
- data/lib/aws-sdk-s3/plugins/md5s.rb +10 -70
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +7 -2
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +5 -7
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
- data/lib/aws-sdk-s3/presigner.rb +9 -7
- data/lib/aws-sdk-s3/resource.rb +127 -22
- data/lib/aws-sdk-s3/transfer_manager.rb +303 -0
- data/lib/aws-sdk-s3/types.rb +8065 -1783
- data/lib/aws-sdk-s3.rb +35 -31
- 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 +2612 -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 +2899 -0
- data/sig/waiters.rbs +95 -0
- metadata +61 -12
- data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +0 -31
|
@@ -7,23 +7,21 @@ module Aws
|
|
|
7
7
|
# @api private
|
|
8
8
|
class FileUploader
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
DEFAULT_MULTIPART_THRESHOLD = 100 * 1024 * 1024
|
|
11
11
|
|
|
12
12
|
# @param [Hash] options
|
|
13
13
|
# @option options [Client] :client
|
|
14
14
|
# @option options [Integer] :multipart_threshold (104857600)
|
|
15
15
|
def initialize(options = {})
|
|
16
|
-
@options = options
|
|
17
16
|
@client = options[:client] || Client.new
|
|
18
|
-
@
|
|
19
|
-
|
|
17
|
+
@executor = options[:executor]
|
|
18
|
+
@multipart_threshold = options[:multipart_threshold] || DEFAULT_MULTIPART_THRESHOLD
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
# @return [Client]
|
|
23
22
|
attr_reader :client
|
|
24
23
|
|
|
25
|
-
# @return [Integer] Files larger than or equal to this in bytes are uploaded
|
|
26
|
-
# using a {MultipartFileUploader}.
|
|
24
|
+
# @return [Integer] Files larger than or equal to this in bytes are uploaded using a {MultipartFileUploader}.
|
|
27
25
|
attr_reader :multipart_threshold
|
|
28
26
|
|
|
29
27
|
# @param [String, Pathname, File, Tempfile] source The file to upload.
|
|
@@ -37,12 +35,10 @@ module Aws
|
|
|
37
35
|
# objects smaller than the multipart threshold.
|
|
38
36
|
# @return [void]
|
|
39
37
|
def upload(source, options = {})
|
|
40
|
-
Aws::Plugins::UserAgent.
|
|
41
|
-
if File.size(source) >= multipart_threshold
|
|
42
|
-
MultipartFileUploader.new(@
|
|
38
|
+
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
|
39
|
+
if File.size(source) >= @multipart_threshold
|
|
40
|
+
MultipartFileUploader.new(client: @client, executor: @executor).upload(source, options)
|
|
43
41
|
else
|
|
44
|
-
# remove multipart parameters not supported by put_object
|
|
45
|
-
options.delete(:thread_count)
|
|
46
42
|
put_object(source, options)
|
|
47
43
|
end
|
|
48
44
|
end
|
|
@@ -50,9 +46,9 @@ module Aws
|
|
|
50
46
|
|
|
51
47
|
private
|
|
52
48
|
|
|
53
|
-
def open_file(source)
|
|
54
|
-
if String
|
|
55
|
-
File.open(source, 'rb'
|
|
49
|
+
def open_file(source, &block)
|
|
50
|
+
if source.is_a?(String) || source.is_a?(Pathname)
|
|
51
|
+
File.open(source, 'rb', &block)
|
|
56
52
|
else
|
|
57
53
|
yield(source)
|
|
58
54
|
end
|
|
@@ -7,34 +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
|
-
COMPLETE_OPTIONS = Set.new(
|
|
25
|
-
Client.api.operation(:complete_multipart_upload).input.shape.member_names
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# @api private
|
|
29
|
-
UPLOAD_PART_OPTIONS = Set.new(
|
|
30
|
-
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
|
|
31
19
|
)
|
|
32
20
|
|
|
33
21
|
# @option options [Client] :client
|
|
34
|
-
# @option options [Integer] :thread_count (THREAD_COUNT)
|
|
35
22
|
def initialize(options = {})
|
|
36
23
|
@client = options[:client] || Client.new
|
|
37
|
-
@
|
|
24
|
+
@executor = options[:executor]
|
|
38
25
|
end
|
|
39
26
|
|
|
40
27
|
# @return [Client]
|
|
@@ -48,13 +35,12 @@ module Aws
|
|
|
48
35
|
# It will be invoked with [bytes_read], [total_sizes]
|
|
49
36
|
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
|
50
37
|
def upload(source, options = {})
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
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)
|
|
58
44
|
end
|
|
59
45
|
|
|
60
46
|
private
|
|
@@ -63,19 +49,21 @@ module Aws
|
|
|
63
49
|
@client.create_multipart_upload(create_opts(options)).upload_id
|
|
64
50
|
end
|
|
65
51
|
|
|
66
|
-
def complete_upload(upload_id, parts, options)
|
|
52
|
+
def complete_upload(upload_id, parts, file_size, options)
|
|
67
53
|
@client.complete_multipart_upload(
|
|
68
|
-
**complete_opts(options)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
54
|
+
**complete_opts(options),
|
|
55
|
+
upload_id: upload_id,
|
|
56
|
+
multipart_upload: { parts: parts },
|
|
57
|
+
mpu_object_size: file_size
|
|
72
58
|
)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
abort_upload(upload_id, options, [e])
|
|
73
61
|
end
|
|
74
62
|
|
|
75
|
-
def upload_parts(upload_id, source, options)
|
|
76
|
-
pending = PartList.new(compute_parts(upload_id, source, options))
|
|
63
|
+
def upload_parts(upload_id, source, file_size, options)
|
|
77
64
|
completed = PartList.new
|
|
78
|
-
|
|
65
|
+
pending = PartList.new(compute_parts(upload_id, source, file_size, options))
|
|
66
|
+
errors = upload_with_executor(pending, completed, options)
|
|
79
67
|
if errors.empty?
|
|
80
68
|
completed.to_a.sort_by { |part| part[:part_number] }
|
|
81
69
|
else
|
|
@@ -84,34 +72,30 @@ module Aws
|
|
|
84
72
|
end
|
|
85
73
|
|
|
86
74
|
def abort_upload(upload_id, options, errors)
|
|
87
|
-
@client.abort_multipart_upload(
|
|
88
|
-
|
|
89
|
-
key: options[:key],
|
|
90
|
-
upload_id: upload_id
|
|
91
|
-
)
|
|
92
|
-
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('; ')}"
|
|
93
77
|
raise MultipartUploadError.new(msg, errors)
|
|
94
|
-
rescue MultipartUploadError =>
|
|
95
|
-
raise
|
|
96
|
-
rescue =>
|
|
97
|
-
msg = "failed to abort multipart upload: #{
|
|
98
|
-
|
|
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])
|
|
99
84
|
end
|
|
100
85
|
|
|
101
|
-
def compute_parts(upload_id, source, options)
|
|
102
|
-
|
|
103
|
-
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)
|
|
104
88
|
offset = 0
|
|
105
89
|
part_number = 1
|
|
106
90
|
parts = []
|
|
107
|
-
while offset <
|
|
91
|
+
while offset < file_size
|
|
108
92
|
parts << upload_part_opts(options).merge(
|
|
109
93
|
upload_id: upload_id,
|
|
110
94
|
part_number: part_number,
|
|
111
95
|
body: FilePart.new(
|
|
112
96
|
source: source,
|
|
113
97
|
offset: offset,
|
|
114
|
-
size: part_size(
|
|
98
|
+
size: part_size(file_size, default_part_size, offset)
|
|
115
99
|
)
|
|
116
100
|
)
|
|
117
101
|
part_number += 1
|
|
@@ -120,69 +104,79 @@ module Aws
|
|
|
120
104
|
parts
|
|
121
105
|
end
|
|
122
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
|
+
|
|
123
119
|
def create_opts(options)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
opts = {}
|
|
121
|
+
unless checksum_not_required?(options)
|
|
122
|
+
opts[:checksum_algorithm] = Aws::Plugins::ChecksumAlgorithm::DEFAULT_CHECKSUM
|
|
127
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) }
|
|
128
126
|
end
|
|
129
127
|
|
|
130
128
|
def complete_opts(options)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
end
|
|
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) }
|
|
135
132
|
end
|
|
136
133
|
|
|
137
134
|
def upload_part_opts(options)
|
|
138
|
-
UPLOAD_PART_OPTIONS.
|
|
139
|
-
|
|
140
|
-
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)
|
|
141
138
|
end
|
|
142
139
|
end
|
|
143
140
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
completed.push(completed_part)
|
|
170
|
-
end
|
|
171
|
-
nil
|
|
172
|
-
rescue => error
|
|
173
|
-
# keep other threads from uploading other parts
|
|
174
|
-
pending.clear!
|
|
175
|
-
error
|
|
176
|
-
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
|
|
177
164
|
end
|
|
178
|
-
thread.abort_on_exception = true
|
|
179
|
-
threads << thread
|
|
180
165
|
end
|
|
181
|
-
|
|
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)
|
|
182
176
|
end
|
|
183
177
|
|
|
184
|
-
def compute_default_part_size(
|
|
185
|
-
[(
|
|
178
|
+
def compute_default_part_size(file_size)
|
|
179
|
+
[(file_size.to_f / MAX_PARTS).ceil, MIN_PART_SIZE].max.to_i
|
|
186
180
|
end
|
|
187
181
|
|
|
188
182
|
def part_size(total_size, part_size, offset)
|
|
@@ -193,9 +187,17 @@ module Aws
|
|
|
193
187
|
end
|
|
194
188
|
end
|
|
195
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
|
+
|
|
196
199
|
# @api private
|
|
197
200
|
class PartList
|
|
198
|
-
|
|
199
201
|
def initialize(parts = [])
|
|
200
202
|
@parts = parts
|
|
201
203
|
@mutex = Mutex.new
|
|
@@ -224,7 +226,6 @@ module Aws
|
|
|
224
226
|
def to_a
|
|
225
227
|
@mutex.synchronize { @parts.dup }
|
|
226
228
|
end
|
|
227
|
-
|
|
228
229
|
end
|
|
229
230
|
|
|
230
231
|
# @api private
|
|
@@ -235,6 +236,8 @@ module Aws
|
|
|
235
236
|
@progress_callback = progress_callback
|
|
236
237
|
end
|
|
237
238
|
|
|
239
|
+
attr_reader :progress_callback
|
|
240
|
+
|
|
238
241
|
def call(part_number, bytes_read)
|
|
239
242
|
# part numbers start at 1
|
|
240
243
|
@bytes_sent[part_number - 1] = bytes_read
|
|
@@ -243,4 +246,4 @@ module Aws
|
|
|
243
246
|
end
|
|
244
247
|
end
|
|
245
248
|
end
|
|
246
|
-
end
|
|
249
|
+
end
|
|
@@ -9,33 +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)
|
|
28
|
-
|
|
29
|
-
# @api private
|
|
30
|
-
COMPLETE_UPLOAD_OPTIONS =
|
|
31
|
-
Set.new(Client.api.operation(:complete_multipart_upload).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)
|
|
32
17
|
|
|
33
18
|
# @option options [Client] :client
|
|
34
19
|
def initialize(options = {})
|
|
35
20
|
@client = options[:client] || Client.new
|
|
21
|
+
@executor = options[:executor]
|
|
36
22
|
@tempfile = options[:tempfile]
|
|
37
|
-
@part_size = options[:part_size] ||
|
|
38
|
-
@thread_count = options[:thread_count] || THREAD_COUNT
|
|
23
|
+
@part_size = options[:part_size] || DEFAULT_PART_SIZE
|
|
39
24
|
end
|
|
40
25
|
|
|
41
26
|
# @return [Client]
|
|
@@ -45,7 +30,7 @@ module Aws
|
|
|
45
30
|
# @option options [required,String] :key
|
|
46
31
|
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
|
47
32
|
def upload(options = {}, &block)
|
|
48
|
-
Aws::Plugins::UserAgent.
|
|
33
|
+
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
|
49
34
|
upload_id = initiate_upload(options)
|
|
50
35
|
parts = upload_parts(upload_id, options, &block)
|
|
51
36
|
complete_upload(upload_id, parts, options)
|
|
@@ -60,85 +45,80 @@ module Aws
|
|
|
60
45
|
|
|
61
46
|
def complete_upload(upload_id, parts, options)
|
|
62
47
|
@client.complete_multipart_upload(
|
|
63
|
-
**complete_opts(options).merge(
|
|
64
|
-
upload_id: upload_id,
|
|
65
|
-
multipart_upload: { parts: parts }
|
|
66
|
-
)
|
|
48
|
+
**complete_opts(options).merge(upload_id: upload_id, multipart_upload: { parts: parts })
|
|
67
49
|
)
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
abort_upload(upload_id, options, [e])
|
|
68
52
|
end
|
|
69
53
|
|
|
70
54
|
def upload_parts(upload_id, options, &block)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
55
|
+
completed_parts = Queue.new
|
|
56
|
+
errors = []
|
|
57
|
+
|
|
58
|
+
begin
|
|
74
59
|
IO.pipe do |read_pipe, write_pipe|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Ensure the pipe is closed to avoid https://github.com/jruby/jruby/issues/6111
|
|
83
|
-
write_pipe.close
|
|
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
|
+
)
|
|
84
67
|
end
|
|
85
|
-
|
|
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
|
|
86
74
|
end
|
|
87
|
-
rescue => e
|
|
88
|
-
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
errors << e
|
|
89
77
|
end
|
|
78
|
+
return ordered_parts(completed_parts) if errors.empty?
|
|
90
79
|
|
|
91
|
-
|
|
92
|
-
Array.new(completed.size) { completed.pop }.sort_by { |part| part[:part_number] }
|
|
93
|
-
else
|
|
94
|
-
abort_upload(upload_id, options, errors)
|
|
95
|
-
end
|
|
80
|
+
abort_upload(upload_id, options, errors)
|
|
96
81
|
end
|
|
97
82
|
|
|
98
83
|
def abort_upload(upload_id, options, errors)
|
|
99
|
-
@client.abort_multipart_upload(
|
|
100
|
-
|
|
101
|
-
key: options[:key],
|
|
102
|
-
upload_id: upload_id
|
|
103
|
-
)
|
|
104
|
-
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('; ')}"
|
|
105
86
|
raise MultipartUploadError.new(msg, errors)
|
|
106
|
-
rescue MultipartUploadError =>
|
|
107
|
-
raise
|
|
108
|
-
rescue =>
|
|
109
|
-
msg = "failed to abort multipart upload: #{
|
|
110
|
-
|
|
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])
|
|
111
93
|
end
|
|
112
94
|
|
|
113
95
|
def create_opts(options)
|
|
114
|
-
CREATE_OPTIONS.
|
|
96
|
+
CREATE_OPTIONS.each_with_object({}) do |key, hash|
|
|
115
97
|
hash[key] = options[key] if options.key?(key)
|
|
116
|
-
hash
|
|
117
98
|
end
|
|
118
99
|
end
|
|
119
100
|
|
|
120
101
|
def upload_part_opts(options)
|
|
121
|
-
UPLOAD_PART_OPTIONS.
|
|
102
|
+
UPLOAD_PART_OPTIONS.each_with_object({}) do |key, hash|
|
|
122
103
|
hash[key] = options[key] if options.key?(key)
|
|
123
|
-
hash
|
|
124
104
|
end
|
|
125
105
|
end
|
|
126
106
|
|
|
127
107
|
def complete_opts(options)
|
|
128
|
-
COMPLETE_UPLOAD_OPTIONS.
|
|
108
|
+
COMPLETE_UPLOAD_OPTIONS.each_with_object({}) do |key, hash|
|
|
129
109
|
hash[key] = options[key] if options.key?(key)
|
|
130
|
-
hash
|
|
131
110
|
end
|
|
132
111
|
end
|
|
133
112
|
|
|
134
113
|
def read_to_part_body(read_pipe)
|
|
135
114
|
return if read_pipe.closed?
|
|
136
|
-
|
|
115
|
+
|
|
116
|
+
temp_io = @tempfile ? Tempfile.new('aws-sdk-s3-upload_stream') : StringIO.new(String.new)
|
|
137
117
|
temp_io.binmode
|
|
138
118
|
bytes_copied = IO.copy_stream(read_pipe, temp_io, @part_size)
|
|
139
119
|
temp_io.rewind
|
|
140
|
-
if bytes_copied
|
|
141
|
-
if Tempfile
|
|
120
|
+
if bytes_copied.zero?
|
|
121
|
+
if temp_io.is_a?(Tempfile)
|
|
142
122
|
temp_io.close
|
|
143
123
|
temp_io.unlink
|
|
144
124
|
end
|
|
@@ -148,52 +128,62 @@ module Aws
|
|
|
148
128
|
end
|
|
149
129
|
end
|
|
150
130
|
|
|
151
|
-
def
|
|
152
|
-
|
|
131
|
+
def upload_with_executor(read_pipe, completed, errors, options)
|
|
132
|
+
completion_queue = Queue.new
|
|
133
|
+
queued_parts = 0
|
|
153
134
|
part_number = 0
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if part[:checksum_algorithm]
|
|
172
|
-
k = "checksum_#{part[:checksum_algorithm].downcase}".to_sym
|
|
173
|
-
completed_part[k] = resp[k]
|
|
174
|
-
end
|
|
175
|
-
completed.push(completed_part)
|
|
176
|
-
ensure
|
|
177
|
-
if Tempfile === body
|
|
178
|
-
body.close
|
|
179
|
-
body.unlink
|
|
180
|
-
elsif StringIO === body
|
|
181
|
-
body.string.clear
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
nil
|
|
186
|
-
rescue => error
|
|
187
|
-
# keep other threads from uploading other parts
|
|
188
|
-
mutex.synchronize do
|
|
189
|
-
thread_errors.push(error)
|
|
190
|
-
read_pipe.close_read unless read_pipe.closed?
|
|
191
|
-
end
|
|
192
|
-
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?
|
|
193
152
|
end
|
|
153
|
+
ensure
|
|
154
|
+
clear_body(body)
|
|
155
|
+
completion_queue << :done
|
|
194
156
|
end
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
197
187
|
end
|
|
198
188
|
end
|
|
199
189
|
end
|