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,14 +1,183 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # WARNING ABOUT GENERATED CODE
2
4
  #
3
5
  # This file is generated. See the contributing guide for more information:
4
- # https://github.com/aws/aws-sdk-ruby/blob/master/CONTRIBUTING.md
6
+ # https://github.com/aws/aws-sdk-ruby/blob/version-3/CONTRIBUTING.md
5
7
  #
6
8
  # WARNING ABOUT GENERATED CODE
7
9
 
8
10
  module Aws::S3
11
+
12
+ # When S3 returns an error response, the Ruby SDK constructs and raises an error.
13
+ # These errors all extend Aws::S3::Errors::ServiceError < {Aws::Errors::ServiceError}
14
+ #
15
+ # You can rescue all S3 errors using ServiceError:
16
+ #
17
+ # begin
18
+ # # do stuff
19
+ # rescue Aws::S3::Errors::ServiceError
20
+ # # rescues all S3 API errors
21
+ # end
22
+ #
23
+ #
24
+ # ## Request Context
25
+ # ServiceError objects have a {Aws::Errors::ServiceError#context #context} method that returns
26
+ # information about the request that generated the error.
27
+ # See {Seahorse::Client::RequestContext} for more information.
28
+ #
29
+ # ## Error Classes
30
+ # * {BucketAlreadyExists}
31
+ # * {BucketAlreadyOwnedByYou}
32
+ # * {EncryptionTypeMismatch}
33
+ # * {InvalidObjectState}
34
+ # * {InvalidRequest}
35
+ # * {InvalidWriteOffset}
36
+ # * {NoSuchBucket}
37
+ # * {NoSuchKey}
38
+ # * {NoSuchUpload}
39
+ # * {ObjectAlreadyInActiveTierError}
40
+ # * {ObjectNotInActiveTierError}
41
+ # * {TooManyParts}
42
+ #
43
+ # Additionally, error classes are dynamically generated for service errors based on the error code
44
+ # if they are not defined above.
9
45
  module Errors
10
46
 
11
47
  extend Aws::Errors::DynamicErrors
12
48
 
49
+ class BucketAlreadyExists < ServiceError
50
+
51
+ # @param [Seahorse::Client::RequestContext] context
52
+ # @param [String] message
53
+ # @param [Aws::S3::Types::BucketAlreadyExists] data
54
+ def initialize(context, message, data = Aws::EmptyStructure.new)
55
+ super(context, message, data)
56
+ end
57
+ end
58
+
59
+ class BucketAlreadyOwnedByYou < ServiceError
60
+
61
+ # @param [Seahorse::Client::RequestContext] context
62
+ # @param [String] message
63
+ # @param [Aws::S3::Types::BucketAlreadyOwnedByYou] data
64
+ def initialize(context, message, data = Aws::EmptyStructure.new)
65
+ super(context, message, data)
66
+ end
67
+ end
68
+
69
+ class EncryptionTypeMismatch < ServiceError
70
+
71
+ # @param [Seahorse::Client::RequestContext] context
72
+ # @param [String] message
73
+ # @param [Aws::S3::Types::EncryptionTypeMismatch] data
74
+ def initialize(context, message, data = Aws::EmptyStructure.new)
75
+ super(context, message, data)
76
+ end
77
+ end
78
+
79
+ class InvalidObjectState < ServiceError
80
+
81
+ # @param [Seahorse::Client::RequestContext] context
82
+ # @param [String] message
83
+ # @param [Aws::S3::Types::InvalidObjectState] data
84
+ def initialize(context, message, data = Aws::EmptyStructure.new)
85
+ super(context, message, data)
86
+ end
87
+
88
+ # @return [String]
89
+ def storage_class
90
+ @data[:storage_class]
91
+ end
92
+
93
+ # @return [String]
94
+ def access_tier
95
+ @data[:access_tier]
96
+ end
97
+ end
98
+
99
+ class InvalidRequest < ServiceError
100
+
101
+ # @param [Seahorse::Client::RequestContext] context
102
+ # @param [String] message
103
+ # @param [Aws::S3::Types::InvalidRequest] data
104
+ def initialize(context, message, data = Aws::EmptyStructure.new)
105
+ super(context, message, data)
106
+ end
107
+ end
108
+
109
+ class InvalidWriteOffset < ServiceError
110
+
111
+ # @param [Seahorse::Client::RequestContext] context
112
+ # @param [String] message
113
+ # @param [Aws::S3::Types::InvalidWriteOffset] data
114
+ def initialize(context, message, data = Aws::EmptyStructure.new)
115
+ super(context, message, data)
116
+ end
117
+ end
118
+
119
+ class NoSuchBucket < ServiceError
120
+
121
+ # @param [Seahorse::Client::RequestContext] context
122
+ # @param [String] message
123
+ # @param [Aws::S3::Types::NoSuchBucket] data
124
+ def initialize(context, message, data = Aws::EmptyStructure.new)
125
+ super(context, message, data)
126
+ end
127
+ end
128
+
129
+ class NoSuchKey < ServiceError
130
+
131
+ # @param [Seahorse::Client::RequestContext] context
132
+ # @param [String] message
133
+ # @param [Aws::S3::Types::NoSuchKey] data
134
+ def initialize(context, message, data = Aws::EmptyStructure.new)
135
+ super(context, message, data)
136
+ end
137
+ end
138
+
139
+ class NoSuchUpload < ServiceError
140
+
141
+ # @param [Seahorse::Client::RequestContext] context
142
+ # @param [String] message
143
+ # @param [Aws::S3::Types::NoSuchUpload] data
144
+ def initialize(context, message, data = Aws::EmptyStructure.new)
145
+ super(context, message, data)
146
+ end
147
+ end
148
+
149
+ class ObjectAlreadyInActiveTierError < ServiceError
150
+
151
+ # @param [Seahorse::Client::RequestContext] context
152
+ # @param [String] message
153
+ # @param [Aws::S3::Types::ObjectAlreadyInActiveTierError] data
154
+ def initialize(context, message, data = Aws::EmptyStructure.new)
155
+ super(context, message, data)
156
+ end
157
+ end
158
+
159
+ class ObjectNotInActiveTierError < ServiceError
160
+
161
+ # @param [Seahorse::Client::RequestContext] context
162
+ # @param [String] message
163
+ # @param [Aws::S3::Types::ObjectNotInActiveTierError] data
164
+ def initialize(context, message, data = Aws::EmptyStructure.new)
165
+ super(context, message, data)
166
+ end
167
+ end
168
+
169
+ class TooManyParts < ServiceError
170
+
171
+ # @param [Seahorse::Client::RequestContext] context
172
+ # @param [String] message
173
+ # @param [Aws::S3::Types::TooManyParts] data
174
+ def initialize(context, message, data = Aws::EmptyStructure.new)
175
+ super(context, message, data)
176
+ end
177
+ end
178
+
13
179
  end
14
180
  end
181
+
182
+ # Load customizations if they exist
183
+ require 'aws-sdk-s3/customizations/errors'
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # WARNING ABOUT GENERATED CODE
2
4
  #
3
5
  # This file is generated. See the contributing guide for more information:
4
- # https://github.com/aws/aws-sdk-ruby/blob/master/CONTRIBUTING.md
6
+ # https://github.com/aws/aws-sdk-ruby/blob/version-3/CONTRIBUTING.md
5
7
  #
6
8
  # WARNING ABOUT GENERATED CODE
7
9
 
@@ -41,6 +43,10 @@ module Aws::S3
41
43
  @event_emitter.on(:initial_response, block) if block_given?
42
44
  end
43
45
 
46
+ def on_unknown_event(&block)
47
+ @event_emitter.on(:unknown_event, block) if block_given?
48
+ end
49
+
44
50
  def on_event(&block)
45
51
  on_records_event(&block)
46
52
  on_stats_event(&block)
@@ -49,6 +55,7 @@ module Aws::S3
49
55
  on_end_event(&block)
50
56
  on_error_event(&block)
51
57
  on_initial_response_event(&block)
58
+ on_unknown_event(&block)
52
59
  end
53
60
 
54
61
  # @api private
@@ -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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
  require 'thread'
3
5
  require 'set'
@@ -21,48 +23,64 @@ module Aws
21
23
 
22
24
  def download(destination, options = {})
23
25
  @path = destination
24
- @mode = options[:mode] || "auto"
26
+ @mode = options[:mode] || 'auto'
25
27
  @thread_count = options[:thread_count] || THREAD_COUNT
26
28
  @chunk_size = options[:chunk_size]
27
29
  @params = {
28
30
  bucket: options[:bucket],
29
- key: options[:key],
31
+ key: options[:key]
30
32
  }
31
33
  @params[:version_id] = options[:version_id] if options[:version_id]
32
-
33
- case @mode
34
- when "auto" then multipart_download
35
- when "single_request" then single_request
36
- when "get_range"
37
- if @chunk_size
38
- resp = @client.head_object(@params)
39
- multithreaded_get_by_ranges(construct_chunks(resp.content_length))
34
+ @on_checksum_validated = options[:on_checksum_validated]
35
+ @progress_callback = options[:progress_callback]
36
+
37
+ validate!
38
+
39
+ Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
40
+ case @mode
41
+ when 'auto' then multipart_download
42
+ when 'single_request' then single_request
43
+ when 'get_range'
44
+ if @chunk_size
45
+ resp = @client.head_object(@params)
46
+ multithreaded_get_by_ranges(resp.content_length)
47
+ else
48
+ msg = 'In :get_range mode, :chunk_size must be provided'
49
+ raise ArgumentError, msg
50
+ end
40
51
  else
41
- msg = "In :get_range mode, :chunk_size must be provided"
52
+ msg = "Invalid mode #{@mode} provided, "\
53
+ 'mode should be :single_request, :get_range or :auto'
42
54
  raise ArgumentError, msg
43
55
  end
44
- else
45
- msg = "Invalid mode #{@mode} provided, "\
46
- "mode should be :single_request, :get_range or :auto"
47
- raise ArgumentError, msg
48
56
  end
49
57
  end
50
58
 
51
59
  private
52
60
 
61
+ def validate!
62
+ if @on_checksum_validated && !@on_checksum_validated.respond_to?(:call)
63
+ raise ArgumentError, 'on_checksum_validated must be callable'
64
+ end
65
+ end
66
+
53
67
  def multipart_download
54
68
  resp = @client.head_object(@params.merge(part_number: 1))
55
69
  count = resp.parts_count
56
70
  if count.nil? || count <= 1
57
- resp.content_length < MIN_CHUNK_SIZE ?
58
- single_request :
59
- multithreaded_get_by_ranges(construct_chunks(resp.content_length))
71
+ if resp.content_length <= MIN_CHUNK_SIZE
72
+ single_request
73
+ else
74
+ multithreaded_get_by_ranges(resp.content_length)
75
+ end
60
76
  else
61
77
  # partNumber is an option
62
78
  resp = @client.head_object(@params)
63
- resp.content_length < MIN_CHUNK_SIZE ?
64
- single_request :
79
+ if resp.content_length <= MIN_CHUNK_SIZE
80
+ single_request
81
+ else
65
82
  compute_mode(resp.content_length, count)
83
+ end
66
84
  end
67
85
  end
68
86
 
@@ -70,9 +88,9 @@ module Aws
70
88
  chunk_size = compute_chunk(file_size)
71
89
  part_size = (file_size.to_f / count.to_f).ceil
72
90
  if chunk_size < part_size
73
- multithreaded_get_by_ranges(construct_chunks(file_size))
91
+ multithreaded_get_by_ranges(file_size)
74
92
  else
75
- multithreaded_get_by_parts(count)
93
+ multithreaded_get_by_parts(count, file_size)
76
94
  end
77
95
  end
78
96
 
@@ -80,10 +98,11 @@ module Aws
80
98
  offset = 0
81
99
  default_chunk_size = compute_chunk(file_size)
82
100
  chunks = []
83
- while offset <= file_size
101
+ while offset < file_size
84
102
  progress = offset + default_chunk_size
85
- chunks << "bytes=#{offset}-#{progress < file_size ? progress : file_size}"
86
- offset = progress + 1
103
+ progress = file_size if progress > file_size
104
+ chunks << "bytes=#{offset}-#{progress - 1}"
105
+ offset = progress
87
106
  end
88
107
  chunks
89
108
  end
@@ -92,7 +111,9 @@ module Aws
92
111
  if @chunk_size && @chunk_size > file_size
93
112
  raise ArgumentError, ":chunk_size shouldn't exceed total file size."
94
113
  else
95
- @chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
114
+ @chunk_size || [
115
+ (file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE
116
+ ].max.to_i
96
117
  end
97
118
  end
98
119
 
@@ -101,39 +122,133 @@ module Aws
101
122
  chunks.each_slice(@thread_count).to_a
102
123
  end
103
124
 
104
- def multithreaded_get_by_ranges(chunks)
105
- thread_batches(chunks, 'range')
125
+ def multithreaded_get_by_ranges(file_size)
126
+ offset = 0
127
+ default_chunk_size = compute_chunk(file_size)
128
+ chunks = []
129
+ part_number = 1 # parts start at 1
130
+ while offset < file_size
131
+ progress = offset + default_chunk_size
132
+ progress = file_size if progress > file_size
133
+ range = "bytes=#{offset}-#{progress - 1}"
134
+ chunks << Part.new(
135
+ part_number: part_number,
136
+ size: (progress-offset),
137
+ params: @params.merge(range: range)
138
+ )
139
+ part_number += 1
140
+ offset = progress
141
+ end
142
+ download_in_threads(PartList.new(chunks), file_size)
106
143
  end
107
144
 
108
- def multithreaded_get_by_parts(parts)
109
- thread_batches(parts, 'part_number')
145
+ def multithreaded_get_by_parts(n_parts, total_size)
146
+ parts = (1..n_parts).map do |part|
147
+ Part.new(part_number: part, params: @params.merge(part_number: part))
148
+ end
149
+ download_in_threads(PartList.new(parts), total_size)
110
150
  end
111
151
 
112
- def thread_batches(chunks, param)
113
- batches(chunks, param).each do |batch|
114
- threads = []
115
- batch.each do |chunk|
116
- threads << Thread.new do
117
- resp = @client.get_object(
118
- @params.merge(param.to_sym => chunk)
119
- )
120
- write(resp)
152
+ def download_in_threads(pending, total_size)
153
+ threads = []
154
+ progress = MultipartProgress.new(pending, total_size, @progress_callback) if @progress_callback
155
+ @thread_count.times do
156
+ thread = Thread.new do
157
+ begin
158
+ while part = pending.shift
159
+ if progress
160
+ part.params[:on_chunk_received] =
161
+ proc do |_chunk, bytes, total|
162
+ progress.call(part.part_number, bytes, total)
163
+ end
164
+ end
165
+ resp = @client.get_object(part.params)
166
+ write(resp)
167
+ if @on_checksum_validated && resp.checksum_validated
168
+ @on_checksum_validated.call(resp.checksum_validated, resp)
169
+ end
170
+ end
171
+ nil
172
+ rescue => error
173
+ # keep other threads from downloading other parts
174
+ pending.clear!
175
+ raise error
121
176
  end
122
177
  end
123
- threads.each(&:join)
178
+ threads << thread
124
179
  end
180
+ threads.map(&:value).compact
125
181
  end
126
182
 
127
183
  def write(resp)
128
- range, _ = resp.content_range.split(" ").last.split("/")
129
- head, _ = range.split("-").map {|s| s.to_i}
130
- IO.write(@path, resp.body.read, head)
184
+ range, _ = resp.content_range.split(' ').last.split('/')
185
+ head, _ = range.split('-').map {|s| s.to_i}
186
+ File.write(@path, resp.body.read, head)
131
187
  end
132
188
 
133
189
  def single_request
134
- @client.get_object(
135
- @params.merge(response_target: @path)
136
- )
190
+ params = @params.merge(response_target: @path)
191
+ params[:on_chunk_received] = single_part_progress if @progress_callback
192
+ resp = @client.get_object(params)
193
+
194
+ return resp unless @on_checksum_validated
195
+
196
+ @on_checksum_validated.call(resp.checksum_validated, resp) if resp.checksum_validated
197
+
198
+ resp
199
+ end
200
+
201
+ def single_part_progress
202
+ proc do |_chunk, bytes_read, total_size|
203
+ @progress_callback.call([bytes_read], [total_size], total_size)
204
+ end
205
+ end
206
+
207
+ class Part < Struct.new(:part_number, :size, :params)
208
+ include Aws::Structure
209
+ end
210
+
211
+ # @api private
212
+ class PartList
213
+ include Enumerable
214
+ def initialize(parts = [])
215
+ @parts = parts
216
+ @mutex = Mutex.new
217
+ end
218
+
219
+ def shift
220
+ @mutex.synchronize { @parts.shift }
221
+ end
222
+
223
+ def size
224
+ @mutex.synchronize { @parts.size }
225
+ end
226
+
227
+ def clear!
228
+ @mutex.synchronize { @parts.clear }
229
+ end
230
+
231
+ def each(&block)
232
+ @mutex.synchronize { @parts.each(&block) }
233
+ end
234
+ end
235
+
236
+ # @api private
237
+ class MultipartProgress
238
+ def initialize(parts, total_size, progress_callback)
239
+ @bytes_received = Array.new(parts.size, 0)
240
+ @part_sizes = parts.map(&:size)
241
+ @total_size = total_size
242
+ @progress_callback = progress_callback
243
+ end
244
+
245
+ def call(part_number, bytes_received, total)
246
+ # part numbers start at 1
247
+ @bytes_received[part_number - 1] = bytes_received
248
+ # part size may not be known until we get the first response
249
+ @part_sizes[part_number - 1] ||= total
250
+ @progress_callback.call(@bytes_received, @part_sizes, @total_size)
251
+ end
137
252
  end
138
253
  end
139
254
  end
@@ -1,15 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
5
 
4
- # A utility class that provides an IO-like interface to a portion of
5
- # a file on disk.
6
+ # A utility class that provides an IO-like interface to a portion of a file
7
+ # on disk.
6
8
  # @api private
7
9
  class FilePart
8
10
 
9
- # @option options [required,String,Pathname,File,Tempfile] :source
10
- # @option options [required,Integer] :offset The file part will read
11
+ # @option options [required, String, Pathname, File, Tempfile] :source
12
+ # The file to upload.
13
+ #
14
+ # @option options [required, Integer] :offset The file part will read
11
15
  # starting at this byte offset.
12
- # @option options [required,Integer] :size The maximum number of bytes to
16
+ #
17
+ # @option options [required, Integer] :size The maximum number of bytes to
13
18
  # read from the `:offset`.
14
19
  def initialize(options = {})
15
20
  @source = options[:source]
@@ -19,7 +24,7 @@ module Aws
19
24
  @file = nil
20
25
  end
21
26
 
22
- # @return [String,Pathname,File,Tempfile]
27
+ # @return [String, Pathname, File, Tempfile]
23
28
  attr_reader :source
24
29
 
25
30
  # @return [Integer]