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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +664 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-s3/access_grants_credentials.rb +57 -0
  5. data/lib/aws-sdk-s3/access_grants_credentials_provider.rb +250 -0
  6. data/lib/aws-sdk-s3/bucket.rb +858 -116
  7. data/lib/aws-sdk-s3/bucket_acl.rb +32 -9
  8. data/lib/aws-sdk-s3/bucket_cors.rb +38 -13
  9. data/lib/aws-sdk-s3/bucket_lifecycle.rb +43 -12
  10. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +100 -13
  11. data/lib/aws-sdk-s3/bucket_logging.rb +35 -6
  12. data/lib/aws-sdk-s3/bucket_notification.rb +27 -9
  13. data/lib/aws-sdk-s3/bucket_policy.rb +79 -10
  14. data/lib/aws-sdk-s3/bucket_region_cache.rb +9 -5
  15. data/lib/aws-sdk-s3/bucket_request_payment.rb +29 -7
  16. data/lib/aws-sdk-s3/bucket_tagging.rb +35 -11
  17. data/lib/aws-sdk-s3/bucket_versioning.rb +108 -17
  18. data/lib/aws-sdk-s3/bucket_website.rb +35 -11
  19. data/lib/aws-sdk-s3/client.rb +11799 -3636
  20. data/lib/aws-sdk-s3/client_api.rb +1201 -276
  21. data/lib/aws-sdk-s3/customizations/bucket.rb +23 -47
  22. data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
  23. data/lib/aws-sdk-s3/customizations/object.rb +216 -70
  24. data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
  25. data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
  26. data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
  27. data/lib/aws-sdk-s3/customizations.rb +27 -29
  28. data/lib/aws-sdk-s3/default_executor.rb +103 -0
  29. data/lib/aws-sdk-s3/encryption/client.rb +6 -2
  30. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +13 -9
  31. data/lib/aws-sdk-s3/encryptionV2/client.rb +6 -2
  32. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +1 -0
  33. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +10 -6
  34. data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
  35. data/lib/aws-sdk-s3/endpoint_provider.rb +716 -0
  36. data/lib/aws-sdk-s3/endpoints.rb +1518 -0
  37. data/lib/aws-sdk-s3/errors.rb +58 -0
  38. data/lib/aws-sdk-s3/express_credentials.rb +55 -0
  39. data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
  40. data/lib/aws-sdk-s3/file_downloader.rb +241 -87
  41. data/lib/aws-sdk-s3/file_uploader.rb +16 -13
  42. data/lib/aws-sdk-s3/legacy_signer.rb +2 -1
  43. data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
  44. data/lib/aws-sdk-s3/multipart_file_uploader.rb +108 -86
  45. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +110 -92
  46. data/lib/aws-sdk-s3/multipart_upload.rb +294 -19
  47. data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
  48. data/lib/aws-sdk-s3/multipart_upload_part.rb +297 -31
  49. data/lib/aws-sdk-s3/object.rb +2224 -269
  50. data/lib/aws-sdk-s3/object_acl.rb +59 -17
  51. data/lib/aws-sdk-s3/object_copier.rb +7 -5
  52. data/lib/aws-sdk-s3/object_multipart_copier.rb +48 -23
  53. data/lib/aws-sdk-s3/object_summary.rb +1915 -220
  54. data/lib/aws-sdk-s3/object_version.rb +450 -58
  55. data/lib/aws-sdk-s3/plugins/accelerate.rb +3 -44
  56. data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
  57. data/lib/aws-sdk-s3/plugins/arn.rb +0 -197
  58. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +3 -39
  59. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +1 -6
  60. data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
  61. data/lib/aws-sdk-s3/plugins/dualstack.rb +1 -55
  62. data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
  63. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +2 -1
  64. data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
  65. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +55 -18
  66. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +6 -29
  67. data/lib/aws-sdk-s3/plugins/location_constraint.rb +3 -1
  68. data/lib/aws-sdk-s3/plugins/md5s.rb +10 -68
  69. data/lib/aws-sdk-s3/plugins/s3_signer.rb +42 -111
  70. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +28 -9
  71. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
  72. data/lib/aws-sdk-s3/presigned_post.rb +99 -78
  73. data/lib/aws-sdk-s3/presigner.rb +32 -41
  74. data/lib/aws-sdk-s3/resource.rb +139 -12
  75. data/lib/aws-sdk-s3/transfer_manager.rb +304 -0
  76. data/lib/aws-sdk-s3/types.rb +10204 -5378
  77. data/lib/aws-sdk-s3.rb +35 -27
  78. data/sig/bucket.rbs +231 -0
  79. data/sig/bucket_acl.rbs +78 -0
  80. data/sig/bucket_cors.rbs +69 -0
  81. data/sig/bucket_lifecycle.rbs +88 -0
  82. data/sig/bucket_lifecycle_configuration.rbs +115 -0
  83. data/sig/bucket_logging.rbs +76 -0
  84. data/sig/bucket_notification.rbs +114 -0
  85. data/sig/bucket_policy.rbs +59 -0
  86. data/sig/bucket_request_payment.rbs +54 -0
  87. data/sig/bucket_tagging.rbs +65 -0
  88. data/sig/bucket_versioning.rbs +77 -0
  89. data/sig/bucket_website.rbs +93 -0
  90. data/sig/client.rbs +2586 -0
  91. data/sig/customizations/bucket.rbs +19 -0
  92. data/sig/customizations/object.rbs +38 -0
  93. data/sig/customizations/object_summary.rbs +35 -0
  94. data/sig/errors.rbs +44 -0
  95. data/sig/multipart_upload.rbs +120 -0
  96. data/sig/multipart_upload_part.rbs +109 -0
  97. data/sig/object.rbs +464 -0
  98. data/sig/object_acl.rbs +86 -0
  99. data/sig/object_summary.rbs +347 -0
  100. data/sig/object_version.rbs +143 -0
  101. data/sig/resource.rbs +141 -0
  102. data/sig/types.rbs +2868 -0
  103. data/sig/waiters.rbs +95 -0
  104. metadata +51 -16
  105. data/lib/aws-sdk-s3/arn/access_point_arn.rb +0 -69
  106. data/lib/aws-sdk-s3/arn/multi_region_access_point_arn.rb +0 -69
  107. data/lib/aws-sdk-s3/arn/object_lambda_arn.rb +0 -69
  108. data/lib/aws-sdk-s3/arn/outpost_access_point_arn.rb +0 -73
  109. 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
- THREAD_COUNT = 10
18
-
19
- # @api private
20
- CREATE_OPTIONS = Set.new(
21
- Client.api.operation(:create_multipart_upload).input.shape.member_names
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
- @thread_count = options[:thread_count] || THREAD_COUNT
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 [void]
36
+ # @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
46
37
  def upload(source, options = {})
47
- if File.size(source) < MIN_PART_SIZE
48
- raise ArgumentError, FILE_TOO_SMALL
49
- else
50
- upload_id = initiate_upload(options)
51
- parts = upload_parts(upload_id, source, options)
52
- complete_upload(upload_id, parts, options)
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
- bucket: options[:bucket],
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
- errors = upload_in_threads(pending, completed, options)
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
- bucket: options[:bucket],
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 => error
91
- raise error
92
- rescue => error
93
- msg = "failed to abort multipart upload: #{error.message}"
94
- raise MultipartUploadError.new(msg, errors + [error])
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
- size = File.size(source)
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 < size
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(size, default_part_size, offset)
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
- CREATE_OPTIONS.inject({}) do |hash, key|
121
- hash[key] = options[key] if options.key?(key)
122
- hash
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.inject({}) do |hash, key|
128
- hash[key] = options[key] if options.key?(key)
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 upload_in_threads(pending, completed, options)
134
- threads = []
135
- if (callback = options[:progress_callback])
136
- progress = MultipartProgress.new(pending, callback)
137
- end
138
- @thread_count.times do
139
- thread = Thread.new do
140
- begin
141
- while part = pending.shift
142
- if progress
143
- part[:on_chunk_sent] =
144
- proc do |_chunk, bytes, _total|
145
- progress.call(part[:part_number], bytes)
146
- end
147
- end
148
- resp = @client.upload_part(part)
149
- part[:body].close
150
- completed.push(etag: resp.etag, part_number: part[:part_number])
151
- end
152
- nil
153
- rescue => error
154
- # keep other threads from uploading other parts
155
- pending.clear!
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
- threads.map(&:value).compact
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(source_size)
166
- [(source_size.to_f / MAX_PARTS).ceil, MIN_PART_SIZE].max.to_i
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
- # api private
16
- THREAD_COUNT = 10
17
-
18
- # api private
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] || 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 [void]
31
+ # @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
43
32
  def upload(options = {}, &block)
44
- upload_id = initiate_upload(options)
45
- parts = upload_parts(upload_id, options, &block)
46
- complete_upload(upload_id, parts, options)
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
- bucket: options[:bucket],
58
- key: options[:key],
59
- upload_id: upload_id,
60
- multipart_upload: { parts: parts })
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
- completed = Queue.new
65
- errors = begin
55
+ completed_parts = Queue.new
56
+ errors = []
57
+
58
+ begin
66
59
  IO.pipe do |read_pipe, write_pipe|
67
- threads = upload_in_threads(read_pipe, completed, upload_part_opts(options).merge(upload_id: upload_id))
68
- begin
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
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
- threads.map(&:value).compact
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
- [e]
75
+ rescue StandardError => e
76
+ errors << e
78
77
  end
78
+ return ordered_parts(completed_parts) if errors.empty?
79
79
 
80
- if errors.empty?
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
- bucket: options[:bucket],
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 => error
96
- raise error
97
- rescue => error
98
- msg = "failed to abort multipart upload: #{error.message}"
99
- raise MultipartUploadError.new(msg, errors + [error])
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.inject({}) do |hash, key|
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.inject({}) do |hash, key|
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
- temp_io = @tempfile ? Tempfile.new(TEMPFILE_PREIX) : StringIO.new(String.new)
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 == 0
123
- if Tempfile === temp_io
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 upload_in_threads(read_pipe, completed, options)
134
- mutex = Mutex.new
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
- @thread_count.times.map do
137
- thread = Thread.new do
138
- begin
139
- loop do
140
- body, thread_part_number = mutex.synchronize do
141
- [read_to_part_body(read_pipe), part_number += 1]
142
- end
143
- break unless (body || thread_part_number == 1)
144
- begin
145
- part = options.merge(
146
- body: body,
147
- part_number: thread_part_number,
148
- )
149
- resp = @client.upload_part(part)
150
- completed << {etag: resp.etag, part_number: part[:part_number]}
151
- ensure
152
- if Tempfile === body
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
- thread.abort_on_exception = true
168
- thread
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