aws-sdk-s3 1.48.0 → 1.169.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1270 -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 +959 -106
  8. data/lib/aws-sdk-s3/bucket_acl.rb +64 -18
  9. data/lib/aws-sdk-s3/bucket_cors.rb +79 -18
  10. data/lib/aws-sdk-s3/bucket_lifecycle.rb +66 -20
  11. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +106 -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 +107 -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 +13765 -1019
  21. data/lib/aws-sdk-s3/client_api.rb +1137 -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 +592 -0
  61. data/lib/aws-sdk-s3/endpoints.rb +1392 -0
  62. data/lib/aws-sdk-s3/errors.rb +126 -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 +176 -44
  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 +82 -23
  71. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +61 -21
  72. data/lib/aws-sdk-s3/multipart_upload.rb +265 -32
  73. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  74. data/lib/aws-sdk-s3/multipart_upload_part.rb +367 -45
  75. data/lib/aws-sdk-s3/object.rb +2475 -228
  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 +2075 -203
  80. data/lib/aws-sdk-s3/object_version.rb +492 -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/dualstack.rb +7 -50
  87. data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
  88. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +5 -4
  89. data/lib/aws-sdk-s3/plugins/express_session_auth.rb +97 -0
  90. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
  91. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +60 -15
  92. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +44 -0
  93. data/lib/aws-sdk-s3/plugins/location_constraint.rb +5 -1
  94. data/lib/aws-sdk-s3/plugins/md5s.rb +35 -30
  95. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  96. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  97. data/lib/aws-sdk-s3/plugins/s3_signer.rb +63 -94
  98. data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +31 -0
  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 +138 -59
  104. data/lib/aws-sdk-s3/resource.rb +155 -17
  105. data/lib/aws-sdk-s3/types.rb +12229 -4377
  106. data/lib/aws-sdk-s3/waiters.rb +67 -1
  107. data/lib/aws-sdk-s3.rb +46 -32
  108. data/sig/bucket.rbs +216 -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 +2406 -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 +34 -0
  125. data/sig/multipart_upload.rbs +111 -0
  126. data/sig/multipart_upload_part.rbs +105 -0
  127. data/sig/object.rbs +443 -0
  128. data/sig/object_acl.rbs +86 -0
  129. data/sig/object_summary.rbs +335 -0
  130. data/sig/object_version.rbs +137 -0
  131. data/sig/resource.rbs +132 -0
  132. data/sig/types.rbs +2596 -0
  133. data/sig/waiters.rbs +95 -0
  134. metadata +74 -15
@@ -1,14 +1,139 @@
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
+ # * {InvalidObjectState}
33
+ # * {NoSuchBucket}
34
+ # * {NoSuchKey}
35
+ # * {NoSuchUpload}
36
+ # * {ObjectAlreadyInActiveTierError}
37
+ # * {ObjectNotInActiveTierError}
38
+ #
39
+ # Additionally, error classes are dynamically generated for service errors based on the error code
40
+ # if they are not defined above.
9
41
  module Errors
10
42
 
11
43
  extend Aws::Errors::DynamicErrors
12
44
 
45
+ class BucketAlreadyExists < ServiceError
46
+
47
+ # @param [Seahorse::Client::RequestContext] context
48
+ # @param [String] message
49
+ # @param [Aws::S3::Types::BucketAlreadyExists] data
50
+ def initialize(context, message, data = Aws::EmptyStructure.new)
51
+ super(context, message, data)
52
+ end
53
+ end
54
+
55
+ class BucketAlreadyOwnedByYou < ServiceError
56
+
57
+ # @param [Seahorse::Client::RequestContext] context
58
+ # @param [String] message
59
+ # @param [Aws::S3::Types::BucketAlreadyOwnedByYou] data
60
+ def initialize(context, message, data = Aws::EmptyStructure.new)
61
+ super(context, message, data)
62
+ end
63
+ end
64
+
65
+ class InvalidObjectState < ServiceError
66
+
67
+ # @param [Seahorse::Client::RequestContext] context
68
+ # @param [String] message
69
+ # @param [Aws::S3::Types::InvalidObjectState] data
70
+ def initialize(context, message, data = Aws::EmptyStructure.new)
71
+ super(context, message, data)
72
+ end
73
+
74
+ # @return [String]
75
+ def storage_class
76
+ @data[:storage_class]
77
+ end
78
+
79
+ # @return [String]
80
+ def access_tier
81
+ @data[:access_tier]
82
+ end
83
+ end
84
+
85
+ class NoSuchBucket < ServiceError
86
+
87
+ # @param [Seahorse::Client::RequestContext] context
88
+ # @param [String] message
89
+ # @param [Aws::S3::Types::NoSuchBucket] data
90
+ def initialize(context, message, data = Aws::EmptyStructure.new)
91
+ super(context, message, data)
92
+ end
93
+ end
94
+
95
+ class NoSuchKey < ServiceError
96
+
97
+ # @param [Seahorse::Client::RequestContext] context
98
+ # @param [String] message
99
+ # @param [Aws::S3::Types::NoSuchKey] data
100
+ def initialize(context, message, data = Aws::EmptyStructure.new)
101
+ super(context, message, data)
102
+ end
103
+ end
104
+
105
+ class NoSuchUpload < ServiceError
106
+
107
+ # @param [Seahorse::Client::RequestContext] context
108
+ # @param [String] message
109
+ # @param [Aws::S3::Types::NoSuchUpload] data
110
+ def initialize(context, message, data = Aws::EmptyStructure.new)
111
+ super(context, message, data)
112
+ end
113
+ end
114
+
115
+ class ObjectAlreadyInActiveTierError < ServiceError
116
+
117
+ # @param [Seahorse::Client::RequestContext] context
118
+ # @param [String] message
119
+ # @param [Aws::S3::Types::ObjectAlreadyInActiveTierError] data
120
+ def initialize(context, message, data = Aws::EmptyStructure.new)
121
+ super(context, message, data)
122
+ end
123
+ end
124
+
125
+ class ObjectNotInActiveTierError < ServiceError
126
+
127
+ # @param [Seahorse::Client::RequestContext] context
128
+ # @param [String] message
129
+ # @param [Aws::S3::Types::ObjectNotInActiveTierError] data
130
+ def initialize(context, message, data = Aws::EmptyStructure.new)
131
+ super(context, message, data)
132
+ end
133
+ end
134
+
13
135
  end
14
136
  end
137
+
138
+ # Load customizations if they exist
139
+ 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,7 +23,7 @@ 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 = {
@@ -30,39 +32,68 @@ module Aws
30
32
  }
31
33
  @params[:version_id] = options[:version_id] if options[:version_id]
32
34
 
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))
35
+ # checksum_mode only supports the value "ENABLED"
36
+ # falsey values (false/nil) or "DISABLED" should be considered
37
+ # disabled and the api parameter should be unset.
38
+ if (checksum_mode = options.fetch(:checksum_mode, 'ENABLED'))
39
+ @params[:checksum_mode] = checksum_mode unless checksum_mode.upcase == 'DISABLED'
40
+ end
41
+ @on_checksum_validated = options[:on_checksum_validated]
42
+
43
+ @progress_callback = options[:progress_callback]
44
+
45
+ validate!
46
+
47
+ Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
48
+ case @mode
49
+ when 'auto' then multipart_download
50
+ when 'single_request' then single_request
51
+ when 'get_range'
52
+ if @chunk_size
53
+ resp = @client.head_object(@params)
54
+ multithreaded_get_by_ranges(resp.content_length)
55
+ else
56
+ msg = 'In :get_range mode, :chunk_size must be provided'
57
+ raise ArgumentError, msg
58
+ end
40
59
  else
41
- msg = "In :get_range mode, :chunk_size must be provided"
60
+ msg = "Invalid mode #{@mode} provided, "\
61
+ 'mode should be :single_request, :get_range or :auto'
42
62
  raise ArgumentError, msg
43
63
  end
44
- else
45
- msg = "Invalid mode #{@mode} provided, "\
46
- "mode should be :single_request, :get_range or :auto"
47
- raise ArgumentError, msg
48
64
  end
49
65
  end
50
66
 
51
67
  private
52
68
 
69
+ def validate!
70
+ if @on_checksum_validated && @params[:checksum_mode] != 'ENABLED'
71
+ raise ArgumentError, "You must set checksum_mode: 'ENABLED' " +
72
+ "when providing a on_checksum_validated callback"
73
+ end
74
+
75
+ if @on_checksum_validated && !@on_checksum_validated.respond_to?(:call)
76
+ raise ArgumentError, 'on_checksum_validated must be callable'
77
+ end
78
+ end
79
+
53
80
  def multipart_download
54
81
  resp = @client.head_object(@params.merge(part_number: 1))
55
82
  count = resp.parts_count
56
83
  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))
84
+ if resp.content_length <= MIN_CHUNK_SIZE
85
+ single_request
86
+ else
87
+ multithreaded_get_by_ranges(resp.content_length)
88
+ end
60
89
  else
61
90
  # partNumber is an option
62
91
  resp = @client.head_object(@params)
63
- resp.content_length < MIN_CHUNK_SIZE ?
64
- single_request :
92
+ if resp.content_length <= MIN_CHUNK_SIZE
93
+ single_request
94
+ else
65
95
  compute_mode(resp.content_length, count)
96
+ end
66
97
  end
67
98
  end
68
99
 
@@ -70,9 +101,9 @@ module Aws
70
101
  chunk_size = compute_chunk(file_size)
71
102
  part_size = (file_size.to_f / count.to_f).ceil
72
103
  if chunk_size < part_size
73
- multithreaded_get_by_ranges(construct_chunks(file_size))
104
+ multithreaded_get_by_ranges(file_size)
74
105
  else
75
- multithreaded_get_by_parts(count)
106
+ multithreaded_get_by_parts(count, file_size)
76
107
  end
77
108
  end
78
109
 
@@ -80,10 +111,11 @@ module Aws
80
111
  offset = 0
81
112
  default_chunk_size = compute_chunk(file_size)
82
113
  chunks = []
83
- while offset <= file_size
114
+ while offset < file_size
84
115
  progress = offset + default_chunk_size
85
- chunks << "bytes=#{offset}-#{progress < file_size ? progress : file_size}"
86
- offset = progress + 1
116
+ progress = file_size if progress > file_size
117
+ chunks << "bytes=#{offset}-#{progress - 1}"
118
+ offset = progress
87
119
  end
88
120
  chunks
89
121
  end
@@ -92,7 +124,9 @@ module Aws
92
124
  if @chunk_size && @chunk_size > file_size
93
125
  raise ArgumentError, ":chunk_size shouldn't exceed total file size."
94
126
  else
95
- @chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
127
+ @chunk_size || [
128
+ (file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE
129
+ ].max.to_i
96
130
  end
97
131
  end
98
132
 
@@ -101,39 +135,137 @@ module Aws
101
135
  chunks.each_slice(@thread_count).to_a
102
136
  end
103
137
 
104
- def multithreaded_get_by_ranges(chunks)
105
- thread_batches(chunks, 'range')
138
+ def multithreaded_get_by_ranges(file_size)
139
+ offset = 0
140
+ default_chunk_size = compute_chunk(file_size)
141
+ chunks = []
142
+ part_number = 1 # parts start at 1
143
+ while offset < file_size
144
+ progress = offset + default_chunk_size
145
+ progress = file_size if progress > file_size
146
+ range = "bytes=#{offset}-#{progress - 1}"
147
+ chunks << Part.new(
148
+ part_number: part_number,
149
+ size: (progress-offset),
150
+ params: @params.merge(range: range)
151
+ )
152
+ part_number += 1
153
+ offset = progress
154
+ end
155
+ download_in_threads(PartList.new(chunks), file_size)
106
156
  end
107
157
 
108
- def multithreaded_get_by_parts(parts)
109
- thread_batches(parts, 'part_number')
158
+ def multithreaded_get_by_parts(n_parts, total_size)
159
+ parts = (1..n_parts).map do |part|
160
+ Part.new(part_number: part, params: @params.merge(part_number: part))
161
+ end
162
+ download_in_threads(PartList.new(parts), total_size)
110
163
  end
111
164
 
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)
165
+ def download_in_threads(pending, total_size)
166
+ threads = []
167
+ if @progress_callback
168
+ progress = MultipartProgress.new(pending, total_size, @progress_callback)
169
+ end
170
+ @thread_count.times do
171
+ thread = Thread.new do
172
+ begin
173
+ while part = pending.shift
174
+ if progress
175
+ part.params[:on_chunk_received] =
176
+ proc do |_chunk, bytes, total|
177
+ progress.call(part.part_number, bytes, total)
178
+ end
179
+ end
180
+ resp = @client.get_object(part.params)
181
+ write(resp)
182
+ if @on_checksum_validated && resp.checksum_validated
183
+ @on_checksum_validated.call(resp.checksum_validated, resp)
184
+ end
185
+ end
186
+ nil
187
+ rescue => error
188
+ # keep other threads from downloading other parts
189
+ pending.clear!
190
+ raise error
121
191
  end
122
192
  end
123
- threads.each(&:join)
193
+ threads << thread
124
194
  end
195
+ threads.map(&:value).compact
125
196
  end
126
197
 
127
198
  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)
199
+ range, _ = resp.content_range.split(' ').last.split('/')
200
+ head, _ = range.split('-').map {|s| s.to_i}
201
+ File.write(@path, resp.body.read, head)
131
202
  end
132
203
 
133
204
  def single_request
134
- @client.get_object(
135
- @params.merge(response_target: @path)
136
- )
205
+ params = @params.merge(response_target: @path)
206
+ params[:on_chunk_received] = single_part_progress if @progress_callback
207
+ resp = @client.get_object(params)
208
+
209
+ return resp unless @on_checksum_validated
210
+
211
+ if resp.checksum_validated
212
+ @on_checksum_validated.call(resp.checksum_validated, resp)
213
+ end
214
+
215
+ resp
216
+ end
217
+
218
+ def single_part_progress
219
+ proc do |_chunk, bytes_read, total_size|
220
+ @progress_callback.call([bytes_read], [total_size], total_size)
221
+ end
222
+ end
223
+
224
+ class Part < Struct.new(:part_number, :size, :params)
225
+ include Aws::Structure
226
+ end
227
+
228
+ # @api private
229
+ class PartList
230
+ include Enumerable
231
+ def initialize(parts = [])
232
+ @parts = parts
233
+ @mutex = Mutex.new
234
+ end
235
+
236
+ def shift
237
+ @mutex.synchronize { @parts.shift }
238
+ end
239
+
240
+ def size
241
+ @mutex.synchronize { @parts.size }
242
+ end
243
+
244
+ def clear!
245
+ @mutex.synchronize { @parts.clear }
246
+ end
247
+
248
+ def each(&block)
249
+ @mutex.synchronize { @parts.each(&block) }
250
+ end
251
+ end
252
+
253
+ # @api private
254
+ class MultipartProgress
255
+ def initialize(parts, total_size, progress_callback)
256
+ @bytes_received = Array.new(parts.size, 0)
257
+ @part_sizes = parts.map(&:size)
258
+ @total_size = total_size
259
+ @progress_callback = progress_callback
260
+ end
261
+
262
+ def call(part_number, bytes_received, total)
263
+ # part numbers start at 1
264
+ @bytes_received[part_number - 1] = bytes_received
265
+ # part size may not be known until we get the first response
266
+ @part_sizes[part_number - 1] ||= total
267
+ @progress_callback.call(@bytes_received, @part_sizes, @total_size)
268
+ end
137
269
  end
138
270
  end
139
271
  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]