aws-sdk-s3 1.128.0 → 1.199.1

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +450 -1
  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 +720 -128
  7. data/lib/aws-sdk-s3/bucket_acl.rb +18 -17
  8. data/lib/aws-sdk-s3/bucket_cors.rb +22 -21
  9. data/lib/aws-sdk-s3/bucket_lifecycle.rb +23 -18
  10. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +76 -19
  11. data/lib/aws-sdk-s3/bucket_logging.rb +21 -14
  12. data/lib/aws-sdk-s3/bucket_notification.rb +6 -6
  13. data/lib/aws-sdk-s3/bucket_policy.rb +65 -20
  14. data/lib/aws-sdk-s3/bucket_region_cache.rb +9 -5
  15. data/lib/aws-sdk-s3/bucket_request_payment.rb +15 -15
  16. data/lib/aws-sdk-s3/bucket_tagging.rb +19 -19
  17. data/lib/aws-sdk-s3/bucket_versioning.rb +41 -41
  18. data/lib/aws-sdk-s3/bucket_website.rb +19 -19
  19. data/lib/aws-sdk-s3/client.rb +9352 -3264
  20. data/lib/aws-sdk-s3/client_api.rb +697 -164
  21. data/lib/aws-sdk-s3/customizations/bucket.rb +1 -1
  22. data/lib/aws-sdk-s3/customizations/errors.rb +16 -3
  23. data/lib/aws-sdk-s3/customizations/object.rb +112 -56
  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.rb +26 -31
  27. data/lib/aws-sdk-s3/encryption/client.rb +2 -2
  28. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +2 -2
  29. data/lib/aws-sdk-s3/encryptionV2/client.rb +2 -2
  30. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +2 -2
  31. data/lib/aws-sdk-s3/endpoint_parameters.rb +54 -15
  32. data/lib/aws-sdk-s3/endpoint_provider.rb +439 -456
  33. data/lib/aws-sdk-s3/endpoints.rb +629 -1261
  34. data/lib/aws-sdk-s3/errors.rb +58 -0
  35. data/lib/aws-sdk-s3/express_credentials.rb +55 -0
  36. data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
  37. data/lib/aws-sdk-s3/file_downloader.rb +156 -69
  38. data/lib/aws-sdk-s3/file_uploader.rb +4 -6
  39. data/lib/aws-sdk-s3/legacy_signer.rb +2 -1
  40. data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
  41. data/lib/aws-sdk-s3/multipart_file_uploader.rb +56 -69
  42. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +84 -91
  43. data/lib/aws-sdk-s3/multipart_upload.rb +179 -26
  44. data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
  45. data/lib/aws-sdk-s3/multipart_upload_part.rb +201 -60
  46. data/lib/aws-sdk-s3/object.rb +2007 -281
  47. data/lib/aws-sdk-s3/object_acl.rb +43 -23
  48. data/lib/aws-sdk-s3/object_copier.rb +1 -1
  49. data/lib/aws-sdk-s3/object_multipart_copier.rb +44 -25
  50. data/lib/aws-sdk-s3/object_summary.rb +1735 -232
  51. data/lib/aws-sdk-s3/object_version.rb +394 -52
  52. data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
  53. data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
  54. data/lib/aws-sdk-s3/plugins/endpoints.rb +32 -208
  55. data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
  56. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +55 -18
  57. data/lib/aws-sdk-s3/plugins/location_constraint.rb +3 -1
  58. data/lib/aws-sdk-s3/plugins/md5s.rb +10 -70
  59. data/lib/aws-sdk-s3/plugins/s3_signer.rb +7 -2
  60. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +5 -7
  61. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
  62. data/lib/aws-sdk-s3/presigned_post.rb +52 -43
  63. data/lib/aws-sdk-s3/presigner.rb +9 -7
  64. data/lib/aws-sdk-s3/resource.rb +127 -22
  65. data/lib/aws-sdk-s3/transfer_manager.rb +252 -0
  66. data/lib/aws-sdk-s3/types.rb +8068 -1887
  67. data/lib/aws-sdk-s3.rb +35 -31
  68. data/sig/bucket.rbs +231 -0
  69. data/sig/bucket_acl.rbs +78 -0
  70. data/sig/bucket_cors.rbs +69 -0
  71. data/sig/bucket_lifecycle.rbs +88 -0
  72. data/sig/bucket_lifecycle_configuration.rbs +115 -0
  73. data/sig/bucket_logging.rbs +76 -0
  74. data/sig/bucket_notification.rbs +114 -0
  75. data/sig/bucket_policy.rbs +59 -0
  76. data/sig/bucket_request_payment.rbs +54 -0
  77. data/sig/bucket_tagging.rbs +65 -0
  78. data/sig/bucket_versioning.rbs +77 -0
  79. data/sig/bucket_website.rbs +93 -0
  80. data/sig/client.rbs +2584 -0
  81. data/sig/customizations/bucket.rbs +19 -0
  82. data/sig/customizations/object.rbs +38 -0
  83. data/sig/customizations/object_summary.rbs +35 -0
  84. data/sig/errors.rbs +44 -0
  85. data/sig/multipart_upload.rbs +120 -0
  86. data/sig/multipart_upload_part.rbs +109 -0
  87. data/sig/object.rbs +462 -0
  88. data/sig/object_acl.rbs +86 -0
  89. data/sig/object_summary.rbs +345 -0
  90. data/sig/object_version.rbs +143 -0
  91. data/sig/resource.rbs +141 -0
  92. data/sig/types.rbs +2866 -0
  93. data/sig/waiters.rbs +95 -0
  94. metadata +44 -12
  95. data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +0 -31
@@ -29,12 +29,17 @@ module Aws::S3
29
29
  # ## Error Classes
30
30
  # * {BucketAlreadyExists}
31
31
  # * {BucketAlreadyOwnedByYou}
32
+ # * {EncryptionTypeMismatch}
33
+ # * {IdempotencyParameterMismatch}
32
34
  # * {InvalidObjectState}
35
+ # * {InvalidRequest}
36
+ # * {InvalidWriteOffset}
33
37
  # * {NoSuchBucket}
34
38
  # * {NoSuchKey}
35
39
  # * {NoSuchUpload}
36
40
  # * {ObjectAlreadyInActiveTierError}
37
41
  # * {ObjectNotInActiveTierError}
42
+ # * {TooManyParts}
38
43
  #
39
44
  # Additionally, error classes are dynamically generated for service errors based on the error code
40
45
  # if they are not defined above.
@@ -62,6 +67,26 @@ module Aws::S3
62
67
  end
63
68
  end
64
69
 
70
+ class EncryptionTypeMismatch < ServiceError
71
+
72
+ # @param [Seahorse::Client::RequestContext] context
73
+ # @param [String] message
74
+ # @param [Aws::S3::Types::EncryptionTypeMismatch] data
75
+ def initialize(context, message, data = Aws::EmptyStructure.new)
76
+ super(context, message, data)
77
+ end
78
+ end
79
+
80
+ class IdempotencyParameterMismatch < ServiceError
81
+
82
+ # @param [Seahorse::Client::RequestContext] context
83
+ # @param [String] message
84
+ # @param [Aws::S3::Types::IdempotencyParameterMismatch] data
85
+ def initialize(context, message, data = Aws::EmptyStructure.new)
86
+ super(context, message, data)
87
+ end
88
+ end
89
+
65
90
  class InvalidObjectState < ServiceError
66
91
 
67
92
  # @param [Seahorse::Client::RequestContext] context
@@ -82,6 +107,26 @@ module Aws::S3
82
107
  end
83
108
  end
84
109
 
110
+ class InvalidRequest < ServiceError
111
+
112
+ # @param [Seahorse::Client::RequestContext] context
113
+ # @param [String] message
114
+ # @param [Aws::S3::Types::InvalidRequest] data
115
+ def initialize(context, message, data = Aws::EmptyStructure.new)
116
+ super(context, message, data)
117
+ end
118
+ end
119
+
120
+ class InvalidWriteOffset < ServiceError
121
+
122
+ # @param [Seahorse::Client::RequestContext] context
123
+ # @param [String] message
124
+ # @param [Aws::S3::Types::InvalidWriteOffset] data
125
+ def initialize(context, message, data = Aws::EmptyStructure.new)
126
+ super(context, message, data)
127
+ end
128
+ end
129
+
85
130
  class NoSuchBucket < ServiceError
86
131
 
87
132
  # @param [Seahorse::Client::RequestContext] context
@@ -132,5 +177,18 @@ module Aws::S3
132
177
  end
133
178
  end
134
179
 
180
+ class TooManyParts < ServiceError
181
+
182
+ # @param [Seahorse::Client::RequestContext] context
183
+ # @param [String] message
184
+ # @param [Aws::S3::Types::TooManyParts] data
185
+ def initialize(context, message, data = Aws::EmptyStructure.new)
186
+ super(context, message, data)
187
+ end
188
+ end
189
+
135
190
  end
136
191
  end
192
+
193
+ # Load customizations if they exist
194
+ require 'aws-sdk-s3/customizations/errors'
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Aws
6
+ module S3
7
+ # @api private
8
+ class ExpressCredentials
9
+ include CredentialProvider
10
+ include RefreshingCredentials
11
+
12
+ SYNC_EXPIRATION_LENGTH = 60 # 1 minute
13
+ ASYNC_EXPIRATION_LENGTH = 120 # 2 minutes
14
+
15
+ def initialize(options = {})
16
+ @client = options[:client]
17
+ @create_session_params = {}
18
+ options.each_pair do |key, value|
19
+ if self.class.create_session_options.include?(key)
20
+ @create_session_params[key] = value
21
+ end
22
+ end
23
+ @async_refresh = true
24
+ super
25
+ end
26
+
27
+ # @return [S3::Client]
28
+ attr_reader :client
29
+
30
+ private
31
+
32
+ def refresh
33
+ c = @client.create_session(@create_session_params).credentials
34
+ @credentials = Credentials.new(
35
+ c.access_key_id,
36
+ c.secret_access_key,
37
+ c.session_token
38
+ )
39
+ @expiration = c.expiration
40
+ end
41
+
42
+ class << self
43
+
44
+ # @api private
45
+ def create_session_options
46
+ @cso ||= begin
47
+ input = S3::Client.api.operation(:create_session).input
48
+ Set.new(input.shape.member_names)
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ # @api private
6
+ def self.express_credentials_cache
7
+ @express_credentials_cache ||= LRUCache.new(max_entries: 100)
8
+ end
9
+
10
+ # Returns Credentials class for S3 Express. Accepts CreateSession
11
+ # params as options. See {Client#create_session} for details.
12
+ class ExpressCredentialsProvider
13
+ # @param [Hash] options
14
+ # @option options [Client] :client The S3 client used to create the
15
+ # session.
16
+ # @option options [String] :session_mode (see: {Client#create_session})
17
+ # @option options [Boolean] :caching (true) When true, credentials will
18
+ # be cached.
19
+ # @option options [Callable] :before_refresh Proc called before
20
+ # credentials are refreshed.
21
+ def initialize(options = {})
22
+ @client = options.delete(:client)
23
+ @caching = options.delete(:caching) != false
24
+ @options = options
25
+ return unless @caching
26
+
27
+ @cache = Aws::S3.express_credentials_cache
28
+ end
29
+
30
+ def express_credentials_for(bucket)
31
+ if @caching
32
+ cached_credentials_for(bucket)
33
+ else
34
+ new_credentials_for(bucket)
35
+ end
36
+ end
37
+
38
+ attr_accessor :client
39
+
40
+ private
41
+
42
+ def cached_credentials_for(bucket)
43
+ if @cache.key?(bucket)
44
+ @cache[bucket]
45
+ else
46
+ @cache[bucket] = new_credentials_for(bucket)
47
+ end
48
+ end
49
+
50
+ def new_credentials_for(bucket)
51
+ ExpressCredentials.new(
52
+ bucket: bucket,
53
+ client: @client,
54
+ **@options
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
- require 'thread'
4
+ require 'securerandom'
5
5
  require 'set'
6
- require 'tmpdir'
7
6
 
8
7
  module Aws
9
8
  module S3
@@ -12,7 +11,6 @@ module Aws
12
11
 
13
12
  MIN_CHUNK_SIZE = 5 * 1024 * 1024
14
13
  MAX_PARTS = 10_000
15
- THREAD_COUNT = 10
16
14
 
17
15
  def initialize(options = {})
18
16
  @client = options[:client] || Client.new
@@ -22,129 +20,218 @@ module Aws
22
20
  attr_reader :client
23
21
 
24
22
  def download(destination, options = {})
25
- @path = destination
26
- @mode = options[:mode] || 'auto'
27
- @thread_count = options[:thread_count] || THREAD_COUNT
28
- @chunk_size = options[:chunk_size]
29
- @params = {
30
- bucket: options[:bucket],
31
- key: options[:key],
32
- }
33
- @params[:version_id] = options[:version_id] if options[:version_id]
34
-
35
- Aws::Plugins::UserAgent.feature('s3-transfer') do
23
+ valid_types = [String, Pathname, File, Tempfile]
24
+ unless valid_types.include?(destination.class)
25
+ raise ArgumentError, "Invalid destination, expected #{valid_types.join(', ')} but got: #{destination.class}"
26
+ end
27
+
28
+ @destination = destination
29
+ @mode = options.delete(:mode) || 'auto'
30
+ @thread_count = options.delete(:thread_count) || 10
31
+ @chunk_size = options.delete(:chunk_size)
32
+ @on_checksum_validated = options.delete(:on_checksum_validated)
33
+ @progress_callback = options.delete(:progress_callback)
34
+ @params = options
35
+ validate!
36
+
37
+ Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
36
38
  case @mode
37
39
  when 'auto' then multipart_download
38
40
  when 'single_request' then single_request
39
41
  when 'get_range'
40
- if @chunk_size
41
- resp = @client.head_object(@params)
42
- multithreaded_get_by_ranges(construct_chunks(resp.content_length))
43
- else
44
- msg = 'In :get_range mode, :chunk_size must be provided'
45
- raise ArgumentError, msg
46
- end
42
+ raise ArgumentError, 'In get_range mode, :chunk_size must be provided' unless @chunk_size
43
+
44
+ resp = @client.head_object(@params)
45
+ multithreaded_get_by_ranges(resp.content_length, resp.etag)
47
46
  else
48
- msg = "Invalid mode #{@mode} provided, "\
49
- 'mode should be :single_request, :get_range or :auto'
50
- raise ArgumentError, msg
47
+ raise ArgumentError, "Invalid mode #{@mode} provided, :mode should be single_request, get_range or auto"
51
48
  end
52
49
  end
50
+ File.rename(@temp_path, @destination) if @temp_path
51
+ ensure
52
+ File.delete(@temp_path) if @temp_path && File.exist?(@temp_path)
53
53
  end
54
54
 
55
55
  private
56
56
 
57
+ def validate!
58
+ return unless @on_checksum_validated && !@on_checksum_validated.respond_to?(:call)
59
+
60
+ raise ArgumentError, ':on_checksum_validated must be callable'
61
+ end
62
+
57
63
  def multipart_download
58
64
  resp = @client.head_object(@params.merge(part_number: 1))
59
65
  count = resp.parts_count
66
+
60
67
  if count.nil? || count <= 1
61
68
  if resp.content_length <= MIN_CHUNK_SIZE
62
69
  single_request
63
70
  else
64
- multithreaded_get_by_ranges(construct_chunks(resp.content_length))
71
+ multithreaded_get_by_ranges(resp.content_length, resp.etag)
65
72
  end
66
73
  else
67
- # partNumber is an option
68
- resp = @client.head_object(@params)
74
+ # covers cases when given object is not uploaded via UploadPart API
75
+ resp = @client.head_object(@params) # partNumber is an option
69
76
  if resp.content_length <= MIN_CHUNK_SIZE
70
77
  single_request
71
78
  else
72
- compute_mode(resp.content_length, count)
79
+ compute_mode(resp.content_length, count, resp.etag)
73
80
  end
74
81
  end
75
82
  end
76
83
 
77
- def compute_mode(file_size, count)
84
+ def compute_mode(file_size, count, etag)
78
85
  chunk_size = compute_chunk(file_size)
79
- part_size = (file_size.to_f / count.to_f).ceil
86
+ part_size = (file_size.to_f / count).ceil
80
87
  if chunk_size < part_size
81
- multithreaded_get_by_ranges(construct_chunks(file_size))
88
+ multithreaded_get_by_ranges(file_size, etag)
82
89
  else
83
- multithreaded_get_by_parts(count)
90
+ multithreaded_get_by_parts(count, file_size, etag)
84
91
  end
85
92
  end
86
93
 
87
- def construct_chunks(file_size)
94
+ def compute_chunk(file_size)
95
+ raise ArgumentError, ":chunk_size shouldn't exceed total file size." if @chunk_size && @chunk_size > file_size
96
+
97
+ @chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
98
+ end
99
+
100
+ def multithreaded_get_by_ranges(file_size, etag)
88
101
  offset = 0
89
102
  default_chunk_size = compute_chunk(file_size)
90
103
  chunks = []
104
+ part_number = 1 # parts start at 1
91
105
  while offset < file_size
92
106
  progress = offset + default_chunk_size
93
107
  progress = file_size if progress > file_size
94
- chunks << "bytes=#{offset}-#{progress - 1}"
108
+ params = @params.merge(range: "bytes=#{offset}-#{progress - 1}", if_match: etag)
109
+ chunks << Part.new(part_number: part_number, size: (progress - offset), params: params)
110
+ part_number += 1
95
111
  offset = progress
96
112
  end
97
- chunks
113
+ download_in_threads(PartList.new(chunks), file_size)
98
114
  end
99
115
 
100
- def compute_chunk(file_size)
101
- if @chunk_size && @chunk_size > file_size
102
- raise ArgumentError, ":chunk_size shouldn't exceed total file size."
103
- else
104
- @chunk_size || [
105
- (file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE
106
- ].max.to_i
116
+ def multithreaded_get_by_parts(n_parts, total_size, etag)
117
+ parts = (1..n_parts).map do |part|
118
+ Part.new(part_number: part, params: @params.merge(part_number: part, if_match: etag))
107
119
  end
120
+ download_in_threads(PartList.new(parts), total_size)
108
121
  end
109
122
 
110
- def batches(chunks, mode)
111
- chunks = (1..chunks) if mode.eql? 'part_number'
112
- chunks.each_slice(@thread_count).to_a
123
+ def download_in_threads(pending, total_size)
124
+ threads = []
125
+ progress = MultipartProgress.new(pending, total_size, @progress_callback) if @progress_callback
126
+ unless [File, Tempfile].include?(@destination.class)
127
+ @temp_path = "#{@destination}.s3tmp.#{SecureRandom.alphanumeric(8)}"
128
+ end
129
+ @thread_count.times do
130
+ thread = Thread.new do
131
+ begin
132
+ while (part = pending.shift)
133
+ if progress
134
+ part.params[:on_chunk_received] =
135
+ proc do |_chunk, bytes, total|
136
+ progress.call(part.part_number, bytes, total)
137
+ end
138
+ end
139
+ resp = @client.get_object(part.params)
140
+ range = extract_range(resp.content_range)
141
+ validate_range(range, part.params[:range]) if part.params[:range]
142
+ write(resp.body, range)
143
+ if @on_checksum_validated && resp.checksum_validated
144
+ @on_checksum_validated.call(resp.checksum_validated, resp)
145
+ end
146
+ end
147
+ nil
148
+ rescue StandardError => e
149
+ pending.clear! # keep other threads from downloading other parts
150
+ raise e
151
+ end
152
+ end
153
+ threads << thread
154
+ end
155
+ threads.map(&:value).compact
113
156
  end
114
157
 
115
- def multithreaded_get_by_ranges(chunks)
116
- thread_batches(chunks, 'range')
158
+ def extract_range(value)
159
+ value.match(%r{bytes (?<range>\d+-\d+)/\d+})[:range]
117
160
  end
118
161
 
119
- def multithreaded_get_by_parts(parts)
120
- thread_batches(parts, 'part_number')
162
+ def validate_range(actual, expected)
163
+ return if actual == expected.match(/bytes=(?<range>\d+-\d+)/)[:range]
164
+
165
+ raise MultipartDownloadError, "multipart download failed: expected range of #{expected} but got #{actual}"
121
166
  end
122
167
 
123
- def thread_batches(chunks, param)
124
- batches(chunks, param).each do |batch|
125
- threads = []
126
- batch.each do |chunk|
127
- threads << Thread.new do
128
- resp = @client.get_object(
129
- @params.merge(param.to_sym => chunk)
130
- )
131
- write(resp)
132
- end
133
- end
134
- threads.each(&:join)
168
+ def write(body, range)
169
+ path = @temp_path || @destination
170
+ File.write(path, body.read, range.split('-').first.to_i)
171
+ end
172
+
173
+ def single_request
174
+ params = @params.merge(response_target: @destination)
175
+ params[:on_chunk_received] = single_part_progress if @progress_callback
176
+ resp = @client.get_object(params)
177
+ return resp unless @on_checksum_validated
178
+
179
+ @on_checksum_validated.call(resp.checksum_validated, resp) if resp.checksum_validated
180
+ resp
181
+ end
182
+
183
+ def single_part_progress
184
+ proc do |_chunk, bytes_read, total_size|
185
+ @progress_callback.call([bytes_read], [total_size], total_size)
135
186
  end
136
187
  end
137
188
 
138
- def write(resp)
139
- range, _ = resp.content_range.split(' ').last.split('/')
140
- head, _ = range.split('-').map {|s| s.to_i}
141
- File.write(@path, resp.body.read, head)
189
+ # @api private
190
+ class Part < Struct.new(:part_number, :size, :params)
191
+ include Aws::Structure
142
192
  end
143
193
 
144
- def single_request
145
- @client.get_object(
146
- @params.merge(response_target: @path)
147
- )
194
+ # @api private
195
+ class PartList
196
+ include Enumerable
197
+ def initialize(parts = [])
198
+ @parts = parts
199
+ @mutex = Mutex.new
200
+ end
201
+
202
+ def shift
203
+ @mutex.synchronize { @parts.shift }
204
+ end
205
+
206
+ def size
207
+ @mutex.synchronize { @parts.size }
208
+ end
209
+
210
+ def clear!
211
+ @mutex.synchronize { @parts.clear }
212
+ end
213
+
214
+ def each(&block)
215
+ @mutex.synchronize { @parts.each(&block) }
216
+ end
217
+ end
218
+
219
+ # @api private
220
+ class MultipartProgress
221
+ def initialize(parts, total_size, progress_callback)
222
+ @bytes_received = Array.new(parts.size, 0)
223
+ @part_sizes = parts.map(&:size)
224
+ @total_size = total_size
225
+ @progress_callback = progress_callback
226
+ end
227
+
228
+ def call(part_number, bytes_received, total)
229
+ # part numbers start at 1
230
+ @bytes_received[part_number - 1] = bytes_received
231
+ # part size may not be known until we get the first response
232
+ @part_sizes[part_number - 1] ||= total
233
+ @progress_callback.call(@bytes_received, @part_sizes, @total_size)
234
+ end
148
235
  end
149
236
  end
150
237
  end
@@ -7,7 +7,7 @@ module Aws
7
7
  # @api private
8
8
  class FileUploader
9
9
 
10
- ONE_HUNDRED_MEGABYTES = 100 * 1024 * 1024
10
+ DEFAULT_MULTIPART_THRESHOLD = 100 * 1024 * 1024
11
11
 
12
12
  # @param [Hash] options
13
13
  # @option options [Client] :client
@@ -15,15 +15,13 @@ module Aws
15
15
  def initialize(options = {})
16
16
  @options = options
17
17
  @client = options[:client] || Client.new
18
- @multipart_threshold = options[:multipart_threshold] ||
19
- ONE_HUNDRED_MEGABYTES
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,7 +35,7 @@ module Aws
37
35
  # objects smaller than the multipart threshold.
38
36
  # @return [void]
39
37
  def upload(source, options = {})
40
- Aws::Plugins::UserAgent.feature('s3-transfer') do
38
+ Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
41
39
  if File.size(source) >= multipart_threshold
42
40
  MultipartFileUploader.new(@options).upload(source, options)
43
41
  else
@@ -3,7 +3,8 @@
3
3
  require 'set'
4
4
  require 'time'
5
5
  require 'openssl'
6
- require 'cgi'
6
+ require "cgi/escape"
7
+ require "cgi/util" if RUBY_VERSION < "3.5"
7
8
  require 'aws-sdk-core/query'
8
9
 
9
10
  module Aws
@@ -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