aws-sdk-s3 1.79.1 → 1.212.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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1548 -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 +900 -98
  8. data/lib/aws-sdk-s3/bucket_acl.rb +44 -10
  9. data/lib/aws-sdk-s3/bucket_cors.rb +51 -11
  10. data/lib/aws-sdk-s3/bucket_lifecycle.rb +53 -8
  11. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +107 -9
  12. data/lib/aws-sdk-s3/bucket_logging.rb +43 -6
  13. data/lib/aws-sdk-s3/bucket_notification.rb +32 -9
  14. data/lib/aws-sdk-s3/bucket_policy.rb +90 -6
  15. data/lib/aws-sdk-s3/bucket_region_cache.rb +9 -5
  16. data/lib/aws-sdk-s3/bucket_request_payment.rb +38 -8
  17. data/lib/aws-sdk-s3/bucket_tagging.rb +46 -7
  18. data/lib/aws-sdk-s3/bucket_versioning.rb +127 -9
  19. data/lib/aws-sdk-s3/bucket_website.rb +46 -7
  20. data/lib/aws-sdk-s3/client.rb +13729 -3146
  21. data/lib/aws-sdk-s3/client_api.rb +1604 -277
  22. data/lib/aws-sdk-s3/customizations/bucket.rb +31 -47
  23. data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
  24. data/lib/aws-sdk-s3/customizations/object.rb +253 -82
  25. data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
  26. data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
  27. data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
  28. data/lib/aws-sdk-s3/customizations.rb +28 -29
  29. data/lib/aws-sdk-s3/default_executor.rb +103 -0
  30. data/lib/aws-sdk-s3/encryption/client.rb +9 -5
  31. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +0 -4
  32. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
  33. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
  34. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +15 -9
  35. data/lib/aws-sdk-s3/encryptionV2/client.rb +105 -26
  36. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -165
  37. data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
  38. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +20 -3
  39. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -4
  40. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
  41. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +18 -6
  42. data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
  43. data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
  44. data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
  45. data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
  46. data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
  47. data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
  48. data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
  49. data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
  50. data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
  51. data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
  52. data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
  53. data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
  54. data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
  55. data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
  56. data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
  57. data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
  58. data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
  59. data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
  60. data/lib/aws-sdk-s3/endpoint_provider.rb +889 -0
  61. data/lib/aws-sdk-s3/endpoints.rb +1544 -0
  62. data/lib/aws-sdk-s3/errors.rb +80 -1
  63. data/lib/aws-sdk-s3/event_streams.rb +1 -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 +258 -82
  67. data/lib/aws-sdk-s3/file_uploader.rb +25 -14
  68. data/lib/aws-sdk-s3/legacy_signer.rb +17 -26
  69. data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
  70. data/lib/aws-sdk-s3/multipart_file_uploader.rb +111 -86
  71. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +110 -92
  72. data/lib/aws-sdk-s3/multipart_upload.rb +304 -14
  73. data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
  74. data/lib/aws-sdk-s3/multipart_upload_part.rb +344 -20
  75. data/lib/aws-sdk-s3/object.rb +2457 -225
  76. data/lib/aws-sdk-s3/object_acl.rb +76 -15
  77. data/lib/aws-sdk-s3/object_copier.rb +7 -5
  78. data/lib/aws-sdk-s3/object_multipart_copier.rb +48 -23
  79. data/lib/aws-sdk-s3/object_summary.rb +2033 -169
  80. data/lib/aws-sdk-s3/object_version.rb +470 -53
  81. data/lib/aws-sdk-s3/plugins/accelerate.rb +1 -39
  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 +3 -41
  85. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +1 -6
  86. data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +44 -0
  87. data/lib/aws-sdk-s3/plugins/dualstack.rb +2 -49
  88. data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
  89. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +3 -1
  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 +1 -1
  92. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +87 -26
  93. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +8 -26
  94. data/lib/aws-sdk-s3/plugins/location_constraint.rb +3 -1
  95. data/lib/aws-sdk-s3/plugins/md5s.rb +10 -68
  96. data/lib/aws-sdk-s3/plugins/s3_signer.rb +48 -88
  97. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +28 -9
  98. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
  99. data/lib/aws-sdk-s3/presigned_post.rb +99 -78
  100. data/lib/aws-sdk-s3/presigner.rb +50 -42
  101. data/lib/aws-sdk-s3/resource.rb +144 -15
  102. data/lib/aws-sdk-s3/transfer_manager.rb +321 -0
  103. data/lib/aws-sdk-s3/types.rb +12223 -4723
  104. data/lib/aws-sdk-s3/waiters.rb +1 -1
  105. data/lib/aws-sdk-s3.rb +37 -28
  106. data/sig/bucket.rbs +231 -0
  107. data/sig/bucket_acl.rbs +78 -0
  108. data/sig/bucket_cors.rbs +69 -0
  109. data/sig/bucket_lifecycle.rbs +88 -0
  110. data/sig/bucket_lifecycle_configuration.rbs +115 -0
  111. data/sig/bucket_logging.rbs +76 -0
  112. data/sig/bucket_notification.rbs +114 -0
  113. data/sig/bucket_policy.rbs +59 -0
  114. data/sig/bucket_request_payment.rbs +54 -0
  115. data/sig/bucket_tagging.rbs +65 -0
  116. data/sig/bucket_versioning.rbs +77 -0
  117. data/sig/bucket_website.rbs +93 -0
  118. data/sig/client.rbs +2612 -0
  119. data/sig/customizations/bucket.rbs +19 -0
  120. data/sig/customizations/object.rbs +38 -0
  121. data/sig/customizations/object_summary.rbs +35 -0
  122. data/sig/errors.rbs +44 -0
  123. data/sig/multipart_upload.rbs +120 -0
  124. data/sig/multipart_upload_part.rbs +109 -0
  125. data/sig/object.rbs +464 -0
  126. data/sig/object_acl.rbs +86 -0
  127. data/sig/object_summary.rbs +347 -0
  128. data/sig/object_version.rbs +143 -0
  129. data/sig/resource.rbs +141 -0
  130. data/sig/types.rbs +2899 -0
  131. data/sig/waiters.rbs +95 -0
  132. metadata +74 -16
  133. data/lib/aws-sdk-s3/plugins/bucket_arn.rb +0 -212
@@ -7,23 +7,22 @@ module Aws
7
7
  # @api private
8
8
  class FileUploader
9
9
 
10
- FIFTEEN_MEGABYTES = 15 * 1024 * 1024
10
+ DEFAULT_MULTIPART_THRESHOLD = 100 * 1024 * 1024
11
11
 
12
12
  # @param [Hash] options
13
13
  # @option options [Client] :client
14
- # @option options [Integer] :multipart_threshold (15728640)
14
+ # @option options [Integer] :multipart_threshold (104857600)
15
15
  def initialize(options = {})
16
- @options = options
17
16
  @client = options[:client] || Client.new
18
- @multipart_threshold = options[:multipart_threshold] ||
19
- FIFTEEN_MEGABYTES
17
+ @executor = options[:executor]
18
+ @http_chunk_size = options[:http_chunk_size]
19
+ @multipart_threshold = options[:multipart_threshold] || DEFAULT_MULTIPART_THRESHOLD
20
20
  end
21
21
 
22
22
  # @return [Client]
23
23
  attr_reader :client
24
24
 
25
- # @return [Integer] Files larger than this in bytes are uploaded
26
- # using a {MultipartFileUploader}.
25
+ # @return [Integer] Files larger than or equal to this in bytes are uploaded using a {MultipartFileUploader}.
27
26
  attr_reader :multipart_threshold
28
27
 
29
28
  # @param [String, Pathname, File, Tempfile] source The file to upload.
@@ -32,20 +31,29 @@ module Aws
32
31
  # @option options [Proc] :progress_callback
33
32
  # A Proc that will be called when each chunk of the upload is sent.
34
33
  # It will be invoked with [bytes_read], [total_sizes]
34
+ # @option options [Integer] :thread_count
35
+ # The thread count to use for multipart uploads. Ignored for
36
+ # objects smaller than the multipart threshold.
35
37
  # @return [void]
36
38
  def upload(source, options = {})
37
- if File.size(source) >= multipart_threshold
38
- MultipartFileUploader.new(@options).upload(source, options)
39
- else
40
- put_object(source, options)
39
+ Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
40
+ if File.size(source) >= @multipart_threshold
41
+ MultipartFileUploader.new(
42
+ client: @client,
43
+ executor: @executor,
44
+ http_chunk_size: @http_chunk_size
45
+ ).upload(source, options)
46
+ else
47
+ put_object(source, options)
48
+ end
41
49
  end
42
50
  end
43
51
 
44
52
  private
45
53
 
46
- def open_file(source)
47
- if String === source || Pathname === source
48
- File.open(source, 'rb') { |file| yield(file) }
54
+ def open_file(source, &block)
55
+ if source.is_a?(String) || source.is_a?(Pathname)
56
+ File.open(source, 'rb', &block)
49
57
  else
50
58
  yield(source)
51
59
  end
@@ -56,7 +64,10 @@ module Aws
56
64
  options[:on_chunk_sent] = single_part_progress(callback)
57
65
  end
58
66
  open_file(source) do |file|
67
+ Thread.current[:net_http_override_body_stream_chunk] = @http_chunk_size if @http_chunk_size
59
68
  @client.put_object(options.merge(body: file))
69
+ ensure
70
+ Thread.current[:net_http_override_body_stream_chunk] = nil
60
71
  end
61
72
  end
62
73
 
@@ -3,8 +3,8 @@
3
3
  require 'set'
4
4
  require 'time'
5
5
  require 'openssl'
6
- require 'cgi'
7
- require 'webrick/httputils'
6
+ require "cgi/escape"
7
+ require "cgi/util" if RUBY_VERSION < "3.5"
8
8
  require 'aws-sdk-core/query'
9
9
 
10
10
  module Aws
@@ -157,33 +157,24 @@ module Aws
157
157
  end
158
158
 
159
159
  def uri_escape(s)
160
-
161
160
  #URI.escape(s)
162
161
 
163
- # URI.escape is deprecated, replacing it with escape from webrick
164
- # to squelch the massive number of warnings generated from Ruby.
165
- # The following script was used to determine the differences
166
- # between the various escape methods available. The webrick
167
- # escape only had two differences and it is available in the
168
- # standard lib.
169
- #
170
- # (0..255).each {|c|
171
- # s = [c].pack("C")
172
- # e = [
173
- # CGI.escape(s),
174
- # ERB::Util.url_encode(s),
175
- # URI.encode_www_form_component(s),
176
- # WEBrick::HTTPUtils.escape_form(s),
177
- # WEBrick::HTTPUtils.escape(s),
178
- # URI.escape(s),
179
- # ]
180
- # next if e.uniq.length == 1
181
- # puts("%5s %5s %5s %5s %5s %5s %5s" % ([s.inspect] + e))
182
- # }
183
- #
184
- WEBrick::HTTPUtils.escape(s).gsub('%5B', '[').gsub('%5D', ']')
162
+ # (0..255).each {|c|
163
+ # s = [c].pack("C")
164
+ # e = [
165
+ # CGI.escape(s),
166
+ # ERB::Util.url_encode(s),
167
+ # URI.encode_www_form_component(s),
168
+ # WEBrick::HTTPUtils.escape_form(s),
169
+ # WEBrick::HTTPUtils.escape(s),
170
+ # URI.escape(s),
171
+ # URI::DEFAULT_PARSER.escape(s)
172
+ # ]
173
+ # next if e.uniq.length == 1
174
+ # puts("%5s %5s %5s %5s %5s %5s %5s %5s" % ([s.inspect] + e))
175
+ # }
176
+ URI::DEFAULT_PARSER.escape(s)
185
177
  end
186
-
187
178
  end
188
179
  end
189
180
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ # Raised when multipart download fails to complete.
6
+ class MultipartDownloadError < StandardError; end
7
+ end
8
+ end
@@ -7,30 +7,22 @@ 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]
25
+ @http_chunk_size = options[:http_chunk_size]
34
26
  end
35
27
 
36
28
  # @return [Client]
@@ -42,15 +34,14 @@ module Aws
42
34
  # @option options [Proc] :progress_callback
43
35
  # A Proc that will be called when each chunk of the upload is sent.
44
36
  # It will be invoked with [bytes_read], [total_sizes]
45
- # @return [void]
37
+ # @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
46
38
  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
39
+ file_size = File.size(source)
40
+ raise ArgumentError, 'unable to multipart upload files smaller than 5MB' if file_size < MIN_PART_SIZE
41
+
42
+ upload_id = initiate_upload(options)
43
+ parts = upload_parts(upload_id, source, file_size, options)
44
+ complete_upload(upload_id, parts, file_size, options)
54
45
  end
55
46
 
56
47
  private
@@ -59,19 +50,21 @@ module Aws
59
50
  @client.create_multipart_upload(create_opts(options)).upload_id
60
51
  end
61
52
 
62
- def complete_upload(upload_id, parts, options)
53
+ def complete_upload(upload_id, parts, file_size, options)
63
54
  @client.complete_multipart_upload(
64
- bucket: options[:bucket],
65
- key: options[:key],
55
+ **complete_opts(options),
66
56
  upload_id: upload_id,
67
- multipart_upload: { parts: parts }
57
+ multipart_upload: { parts: parts },
58
+ mpu_object_size: file_size
68
59
  )
60
+ rescue StandardError => e
61
+ abort_upload(upload_id, options, [e])
69
62
  end
70
63
 
71
- def upload_parts(upload_id, source, options)
72
- pending = PartList.new(compute_parts(upload_id, source, options))
64
+ def upload_parts(upload_id, source, file_size, options)
73
65
  completed = PartList.new
74
- errors = upload_in_threads(pending, completed, options)
66
+ pending = PartList.new(compute_parts(upload_id, source, file_size, options))
67
+ errors = upload_with_executor(pending, completed, options)
75
68
  if errors.empty?
76
69
  completed.to_a.sort_by { |part| part[:part_number] }
77
70
  else
@@ -80,34 +73,30 @@ module Aws
80
73
  end
81
74
 
82
75
  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("; ")}"
76
+ @client.abort_multipart_upload(bucket: options[:bucket], key: options[:key], upload_id: upload_id)
77
+ msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
89
78
  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])
79
+ rescue MultipartUploadError => e
80
+ raise e
81
+ rescue StandardError => e
82
+ msg = "failed to abort multipart upload: #{e&.message}. " \
83
+ "Multipart upload failed: #{errors.map(&:message).join('; ')}"
84
+ raise MultipartUploadError.new(msg, errors + [e])
95
85
  end
96
86
 
97
- def compute_parts(upload_id, source, options)
98
- size = File.size(source)
99
- default_part_size = compute_default_part_size(size)
87
+ def compute_parts(upload_id, source, file_size, options)
88
+ default_part_size = compute_default_part_size(file_size)
100
89
  offset = 0
101
90
  part_number = 1
102
91
  parts = []
103
- while offset < size
92
+ while offset < file_size
104
93
  parts << upload_part_opts(options).merge(
105
94
  upload_id: upload_id,
106
95
  part_number: part_number,
107
96
  body: FilePart.new(
108
97
  source: source,
109
98
  offset: offset,
110
- size: part_size(size, default_part_size, offset)
99
+ size: part_size(file_size, default_part_size, offset)
111
100
  )
112
101
  )
113
102
  part_number += 1
@@ -116,54 +105,81 @@ module Aws
116
105
  parts
117
106
  end
118
107
 
108
+ def checksum_key?(key)
109
+ CHECKSUM_KEYS.include?(key)
110
+ end
111
+
112
+ def has_checksum_key?(keys)
113
+ keys.any? { |key| checksum_key?(key) }
114
+ end
115
+
116
+ def checksum_not_required?(options)
117
+ @client.config.request_checksum_calculation == 'when_required' && !options[:checksum_algorithm]
118
+ end
119
+
119
120
  def create_opts(options)
120
- CREATE_OPTIONS.inject({}) do |hash, key|
121
- hash[key] = options[key] if options.key?(key)
122
- hash
121
+ opts = {}
122
+ unless checksum_not_required?(options)
123
+ opts[:checksum_algorithm] = Aws::Plugins::ChecksumAlgorithm::DEFAULT_CHECKSUM
123
124
  end
125
+ opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
126
+ CREATE_OPTIONS.each_with_object(opts) { |k, h| h[k] = options[k] if options.key?(k) }
127
+ end
128
+
129
+ def complete_opts(options)
130
+ opts = {}
131
+ opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
132
+ COMPLETE_OPTIONS.each_with_object(opts) { |k, h| h[k] = options[k] if options.key?(k) }
124
133
  end
125
134
 
126
135
  def upload_part_opts(options)
127
- UPLOAD_PART_OPTIONS.inject({}) do |hash, key|
128
- hash[key] = options[key] if options.key?(key)
129
- hash
136
+ UPLOAD_PART_OPTIONS.each_with_object({}) do |key, hash|
137
+ # don't pass through checksum calculations
138
+ hash[key] = options[key] if options.key?(key) && !checksum_key?(key)
130
139
  end
131
140
  end
132
141
 
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
142
+ def upload_with_executor(pending, completed, options)
143
+ upload_attempts = 0
144
+ completion_queue = Queue.new
145
+ abort_upload = false
146
+ errors = []
147
+ progress = MultipartProgress.new(pending, options[:progress_callback])
148
+
149
+ while (part = pending.shift)
150
+ break if abort_upload
151
+
152
+ upload_attempts += 1
153
+ @executor.post(part) do |p|
154
+ Thread.current[:net_http_override_body_stream_chunk] = @http_chunk_size if @http_chunk_size
155
+ update_progress(progress, p)
156
+ resp = @client.upload_part(p)
157
+ p[:body].close
158
+ completed_part = { etag: resp.etag, part_number: p[:part_number] }
159
+ apply_part_checksum(resp, completed_part)
160
+ completed.push(completed_part)
161
+ rescue StandardError => e
162
+ abort_upload = true
163
+ errors << e
164
+ ensure
165
+ Thread.current[:net_http_override_body_stream_chunk] = nil if @http_chunk_size
166
+ completion_queue << :done
158
167
  end
159
- thread.abort_on_exception = true
160
- threads << thread
161
168
  end
162
- threads.map(&:value).compact
169
+
170
+ upload_attempts.times { completion_queue.pop }
171
+ errors
172
+ end
173
+
174
+ def apply_part_checksum(resp, part)
175
+ return unless (checksum = resp.context.params[:checksum_algorithm])
176
+
177
+ k = :"checksum_#{checksum.downcase}"
178
+ part[k] = resp.send(k)
163
179
  end
164
180
 
165
- def compute_default_part_size(source_size)
166
- [(source_size.to_f / MAX_PARTS).ceil, MIN_PART_SIZE].max.to_i
181
+ def compute_default_part_size(file_size)
182
+ [(file_size.to_f / MAX_PARTS).ceil, MIN_PART_SIZE].max.to_i
167
183
  end
168
184
 
169
185
  def part_size(total_size, part_size, offset)
@@ -174,9 +190,17 @@ module Aws
174
190
  end
175
191
  end
176
192
 
193
+ def update_progress(progress, part)
194
+ return unless progress.progress_callback
195
+
196
+ part[:on_chunk_sent] =
197
+ proc do |_chunk, bytes, _total|
198
+ progress.call(part[:part_number], bytes)
199
+ end
200
+ end
201
+
177
202
  # @api private
178
203
  class PartList
179
-
180
204
  def initialize(parts = [])
181
205
  @parts = parts
182
206
  @mutex = Mutex.new
@@ -205,7 +229,6 @@ module Aws
205
229
  def to_a
206
230
  @mutex.synchronize { @parts.dup }
207
231
  end
208
-
209
232
  end
210
233
 
211
234
  # @api private
@@ -216,6 +239,8 @@ module Aws
216
239
  @progress_callback = progress_callback
217
240
  end
218
241
 
242
+ attr_reader :progress_callback
243
+
219
244
  def call(part_number, bytes_read)
220
245
  # part numbers start at 1
221
246
  @bytes_sent[part_number - 1] = bytes_read