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.
Files changed (134) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1352 -0
  3. data/LICENSE.txt +202 -0
  4. data/VERSION +1 -0
  5. data/lib/aws-sdk-s3/access_grants_credentials.rb +57 -0
  6. data/lib/aws-sdk-s3/access_grants_credentials_provider.rb +250 -0
  7. data/lib/aws-sdk-s3/bucket.rb +1005 -106
  8. data/lib/aws-sdk-s3/bucket_acl.rb +65 -18
  9. data/lib/aws-sdk-s3/bucket_cors.rb +80 -18
  10. data/lib/aws-sdk-s3/bucket_lifecycle.rb +71 -20
  11. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +126 -21
  12. data/lib/aws-sdk-s3/bucket_logging.rb +68 -16
  13. data/lib/aws-sdk-s3/bucket_notification.rb +52 -20
  14. data/lib/aws-sdk-s3/bucket_policy.rb +108 -17
  15. data/lib/aws-sdk-s3/bucket_region_cache.rb +11 -5
  16. data/lib/aws-sdk-s3/bucket_request_payment.rb +60 -15
  17. data/lib/aws-sdk-s3/bucket_tagging.rb +71 -18
  18. data/lib/aws-sdk-s3/bucket_versioning.rb +133 -17
  19. data/lib/aws-sdk-s3/bucket_website.rb +78 -21
  20. data/lib/aws-sdk-s3/client.rb +14517 -941
  21. data/lib/aws-sdk-s3/client_api.rb +1296 -197
  22. data/lib/aws-sdk-s3/customizations/bucket.rb +56 -37
  23. data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
  24. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
  25. data/lib/aws-sdk-s3/customizations/object.rb +288 -68
  26. data/lib/aws-sdk-s3/customizations/object_summary.rb +10 -0
  27. data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
  28. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
  29. data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
  30. data/lib/aws-sdk-s3/customizations.rb +27 -28
  31. data/lib/aws-sdk-s3/encryption/client.rb +28 -7
  32. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
  33. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +43 -5
  34. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  35. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +13 -2
  36. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  37. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
  38. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
  39. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  40. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  41. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +46 -11
  42. data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
  43. data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
  44. data/lib/aws-sdk-s3/encryption.rb +4 -0
  45. data/lib/aws-sdk-s3/encryptionV2/client.rb +570 -0
  46. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +223 -0
  47. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +170 -0
  48. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
  49. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +65 -0
  50. data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
  51. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
  52. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
  53. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +73 -0
  54. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
  55. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +173 -0
  56. data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
  57. data/lib/aws-sdk-s3/encryptionV2/utils.rb +103 -0
  58. data/lib/aws-sdk-s3/encryption_v2.rb +23 -0
  59. data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
  60. data/lib/aws-sdk-s3/endpoint_provider.rb +716 -0
  61. data/lib/aws-sdk-s3/endpoints.rb +1434 -0
  62. data/lib/aws-sdk-s3/errors.rb +170 -1
  63. data/lib/aws-sdk-s3/event_streams.rb +8 -1
  64. data/lib/aws-sdk-s3/express_credentials.rb +55 -0
  65. data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
  66. data/lib/aws-sdk-s3/file_downloader.rb +161 -46
  67. data/lib/aws-sdk-s3/file_part.rb +11 -6
  68. data/lib/aws-sdk-s3/file_uploader.rb +39 -18
  69. data/lib/aws-sdk-s3/legacy_signer.rb +17 -25
  70. data/lib/aws-sdk-s3/multipart_file_uploader.rb +104 -27
  71. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +61 -21
  72. data/lib/aws-sdk-s3/multipart_upload.rb +342 -32
  73. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  74. data/lib/aws-sdk-s3/multipart_upload_part.rb +384 -46
  75. data/lib/aws-sdk-s3/object.rb +2600 -231
  76. data/lib/aws-sdk-s3/object_acl.rb +103 -25
  77. data/lib/aws-sdk-s3/object_copier.rb +9 -5
  78. data/lib/aws-sdk-s3/object_multipart_copier.rb +48 -22
  79. data/lib/aws-sdk-s3/object_summary.rb +2174 -204
  80. data/lib/aws-sdk-s3/object_version.rb +539 -80
  81. data/lib/aws-sdk-s3/plugins/accelerate.rb +17 -64
  82. data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
  83. data/lib/aws-sdk-s3/plugins/arn.rb +70 -0
  84. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +7 -43
  85. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +20 -3
  86. data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
  87. data/lib/aws-sdk-s3/plugins/dualstack.rb +7 -50
  88. data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
  89. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +5 -4
  90. data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
  91. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
  92. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +62 -17
  93. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +44 -0
  94. data/lib/aws-sdk-s3/plugins/location_constraint.rb +5 -1
  95. data/lib/aws-sdk-s3/plugins/md5s.rb +14 -70
  96. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  97. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  98. data/lib/aws-sdk-s3/plugins/s3_signer.rb +63 -94
  99. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
  100. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +139 -0
  101. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
  102. data/lib/aws-sdk-s3/presigned_post.rb +160 -99
  103. data/lib/aws-sdk-s3/presigner.rb +141 -62
  104. data/lib/aws-sdk-s3/resource.rb +156 -17
  105. data/lib/aws-sdk-s3/types.rb +13021 -4106
  106. data/lib/aws-sdk-s3/waiters.rb +67 -1
  107. data/lib/aws-sdk-s3.rb +46 -32
  108. data/sig/bucket.rbs +222 -0
  109. data/sig/bucket_acl.rbs +78 -0
  110. data/sig/bucket_cors.rbs +69 -0
  111. data/sig/bucket_lifecycle.rbs +88 -0
  112. data/sig/bucket_lifecycle_configuration.rbs +115 -0
  113. data/sig/bucket_logging.rbs +76 -0
  114. data/sig/bucket_notification.rbs +114 -0
  115. data/sig/bucket_policy.rbs +59 -0
  116. data/sig/bucket_request_payment.rbs +54 -0
  117. data/sig/bucket_tagging.rbs +65 -0
  118. data/sig/bucket_versioning.rbs +77 -0
  119. data/sig/bucket_website.rbs +93 -0
  120. data/sig/client.rbs +2472 -0
  121. data/sig/customizations/bucket.rbs +19 -0
  122. data/sig/customizations/object.rbs +38 -0
  123. data/sig/customizations/object_summary.rbs +35 -0
  124. data/sig/errors.rbs +42 -0
  125. data/sig/multipart_upload.rbs +120 -0
  126. data/sig/multipart_upload_part.rbs +109 -0
  127. data/sig/object.rbs +459 -0
  128. data/sig/object_acl.rbs +86 -0
  129. data/sig/object_summary.rbs +345 -0
  130. data/sig/object_version.rbs +143 -0
  131. data/sig/resource.rbs +134 -0
  132. data/sig/types.rbs +2712 -0
  133. data/sig/waiters.rbs +95 -0
  134. 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
- FIFTEEN_MEGABYTES = 15 * 1024 * 1024
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 Files greater than
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] || FIFTEEN_MEGABYTES
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
- if File.size(source) >= multipart_threshold
32
- MultipartFileUploader.new(@options).upload(source, options)
33
- else
34
- put_object(source, options)
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
- # URI.escape is deprecated, replacing it with escape from webrick
162
- # to squelch the massive number of warnings generated from Ruby.
163
- # The following script was used to determine the differences
164
- # between the various escape methods available. The webrick
165
- # escape only had two differences and it is available in the
166
- # standard lib.
167
- #
168
- # (0..255).each {|c|
169
- # s = [c].pack("C")
170
- # e = [
171
- # CGI.escape(s),
172
- # ERB::Util.url_encode(s),
173
- # URI.encode_www_form_component(s),
174
- # WEBrick::HTTPUtils.escape_form(s),
175
- # WEBrick::HTTPUtils.escape(s),
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
- # @api private
19
- CREATE_OPTIONS =
20
- Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
19
+ CREATE_OPTIONS = Set.new(
20
+ Client.api.operation(:create_multipart_upload).input.shape.member_names
21
+ )
21
22
 
22
- # @api private
23
- UPLOAD_PART_OPTIONS =
24
- Set.new(Client.api.operation(:upload_part).input.shape.member_names)
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
- # @return [void]
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
- bucket: options[:bucket],
58
- key: options[:key],
59
- upload_id: upload_id,
60
- multipart_upload: { parts: parts })
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
- CREATE_OPTIONS.inject({}) do |hash, key|
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
- hash[key] = options[key] if options.key?(key)
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
- @thread_count.times do
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
- completed.push(etag: resp.etag, part_number: part[:part_number])
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
- # @return [void]
46
+ # @option options [Integer] :thread_count (THREAD_COUNT)
47
+ # @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
41
48
  def upload(options = {}, &block)
42
- upload_id = initiate_upload(options)
43
- parts = upload_parts(upload_id, options, &block)
44
- complete_upload(upload_id, parts, options)
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
- bucket: options[:bucket],
56
- key: options[:key],
57
- upload_id: upload_id,
58
- multipart_upload: { parts: parts })
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
- errors = IO.pipe do |read_pipe, write_pipe|
64
- threads = upload_in_threads(read_pipe, completed, upload_part_opts(options).merge(upload_id: upload_id))
65
- block.call(write_pipe)
66
- write_pipe.close
67
- threads.map(&:value).compact
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
- completed << {etag: resp.etag, part_number: part[:part_number]}
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 { read_pipe.close_read }
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