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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1548 -0
- data/LICENSE.txt +202 -0
- data/VERSION +1 -0
- data/lib/aws-sdk-s3/access_grants_credentials.rb +57 -0
- data/lib/aws-sdk-s3/access_grants_credentials_provider.rb +250 -0
- data/lib/aws-sdk-s3/bucket.rb +900 -98
- data/lib/aws-sdk-s3/bucket_acl.rb +44 -10
- data/lib/aws-sdk-s3/bucket_cors.rb +51 -11
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +53 -8
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +107 -9
- data/lib/aws-sdk-s3/bucket_logging.rb +43 -6
- data/lib/aws-sdk-s3/bucket_notification.rb +32 -9
- data/lib/aws-sdk-s3/bucket_policy.rb +90 -6
- data/lib/aws-sdk-s3/bucket_region_cache.rb +9 -5
- data/lib/aws-sdk-s3/bucket_request_payment.rb +38 -8
- data/lib/aws-sdk-s3/bucket_tagging.rb +46 -7
- data/lib/aws-sdk-s3/bucket_versioning.rb +127 -9
- data/lib/aws-sdk-s3/bucket_website.rb +46 -7
- data/lib/aws-sdk-s3/client.rb +13729 -3146
- data/lib/aws-sdk-s3/client_api.rb +1604 -277
- data/lib/aws-sdk-s3/customizations/bucket.rb +31 -47
- data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
- data/lib/aws-sdk-s3/customizations/object.rb +253 -82
- data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
- data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
- data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
- data/lib/aws-sdk-s3/customizations.rb +28 -29
- data/lib/aws-sdk-s3/default_executor.rb +103 -0
- data/lib/aws-sdk-s3/encryption/client.rb +9 -5
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +0 -4
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +15 -9
- data/lib/aws-sdk-s3/encryptionV2/client.rb +105 -26
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -165
- data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +20 -3
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -4
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +18 -6
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
- data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
- data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
- data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
- data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
- data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
- data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
- data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
- data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
- data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
- data/lib/aws-sdk-s3/endpoint_parameters.rb +181 -0
- data/lib/aws-sdk-s3/endpoint_provider.rb +889 -0
- data/lib/aws-sdk-s3/endpoints.rb +1544 -0
- data/lib/aws-sdk-s3/errors.rb +80 -1
- data/lib/aws-sdk-s3/event_streams.rb +1 -1
- data/lib/aws-sdk-s3/express_credentials.rb +55 -0
- data/lib/aws-sdk-s3/express_credentials_provider.rb +59 -0
- data/lib/aws-sdk-s3/file_downloader.rb +258 -82
- data/lib/aws-sdk-s3/file_uploader.rb +25 -14
- data/lib/aws-sdk-s3/legacy_signer.rb +17 -26
- data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +111 -86
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +110 -92
- data/lib/aws-sdk-s3/multipart_upload.rb +304 -14
- data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
- data/lib/aws-sdk-s3/multipart_upload_part.rb +344 -20
- data/lib/aws-sdk-s3/object.rb +2457 -225
- data/lib/aws-sdk-s3/object_acl.rb +76 -15
- data/lib/aws-sdk-s3/object_copier.rb +7 -5
- data/lib/aws-sdk-s3/object_multipart_copier.rb +48 -23
- data/lib/aws-sdk-s3/object_summary.rb +2033 -169
- data/lib/aws-sdk-s3/object_version.rb +470 -53
- data/lib/aws-sdk-s3/plugins/accelerate.rb +1 -39
- data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
- data/lib/aws-sdk-s3/plugins/arn.rb +70 -0
- data/lib/aws-sdk-s3/plugins/bucket_dns.rb +3 -41
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +1 -6
- data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +44 -0
- data/lib/aws-sdk-s3/plugins/dualstack.rb +2 -49
- data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +3 -1
- data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +1 -1
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +87 -26
- data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +8 -26
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +3 -1
- data/lib/aws-sdk-s3/plugins/md5s.rb +10 -68
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +48 -88
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +28 -9
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
- data/lib/aws-sdk-s3/presigned_post.rb +99 -78
- data/lib/aws-sdk-s3/presigner.rb +50 -42
- data/lib/aws-sdk-s3/resource.rb +144 -15
- data/lib/aws-sdk-s3/transfer_manager.rb +321 -0
- data/lib/aws-sdk-s3/types.rb +12223 -4723
- data/lib/aws-sdk-s3/waiters.rb +1 -1
- data/lib/aws-sdk-s3.rb +37 -28
- data/sig/bucket.rbs +231 -0
- data/sig/bucket_acl.rbs +78 -0
- data/sig/bucket_cors.rbs +69 -0
- data/sig/bucket_lifecycle.rbs +88 -0
- data/sig/bucket_lifecycle_configuration.rbs +115 -0
- data/sig/bucket_logging.rbs +76 -0
- data/sig/bucket_notification.rbs +114 -0
- data/sig/bucket_policy.rbs +59 -0
- data/sig/bucket_request_payment.rbs +54 -0
- data/sig/bucket_tagging.rbs +65 -0
- data/sig/bucket_versioning.rbs +77 -0
- data/sig/bucket_website.rbs +93 -0
- data/sig/client.rbs +2612 -0
- data/sig/customizations/bucket.rbs +19 -0
- data/sig/customizations/object.rbs +38 -0
- data/sig/customizations/object_summary.rbs +35 -0
- data/sig/errors.rbs +44 -0
- data/sig/multipart_upload.rbs +120 -0
- data/sig/multipart_upload_part.rbs +109 -0
- data/sig/object.rbs +464 -0
- data/sig/object_acl.rbs +86 -0
- data/sig/object_summary.rbs +347 -0
- data/sig/object_version.rbs +143 -0
- data/sig/resource.rbs +141 -0
- data/sig/types.rbs +2899 -0
- data/sig/waiters.rbs +95 -0
- metadata +74 -16
- data/lib/aws-sdk-s3/plugins/bucket_arn.rb +0 -212
data/lib/aws-sdk-s3/errors.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# WARNING ABOUT GENERATED CODE
|
|
4
4
|
#
|
|
5
5
|
# This file is generated. See the contributing guide for more information:
|
|
6
|
-
# https://github.com/aws/aws-sdk-ruby/blob/
|
|
6
|
+
# https://github.com/aws/aws-sdk-ruby/blob/version-3/CONTRIBUTING.md
|
|
7
7
|
#
|
|
8
8
|
# WARNING ABOUT GENERATED CODE
|
|
9
9
|
|
|
@@ -29,11 +29,17 @@ module Aws::S3
|
|
|
29
29
|
# ## Error Classes
|
|
30
30
|
# * {BucketAlreadyExists}
|
|
31
31
|
# * {BucketAlreadyOwnedByYou}
|
|
32
|
+
# * {EncryptionTypeMismatch}
|
|
33
|
+
# * {IdempotencyParameterMismatch}
|
|
34
|
+
# * {InvalidObjectState}
|
|
35
|
+
# * {InvalidRequest}
|
|
36
|
+
# * {InvalidWriteOffset}
|
|
32
37
|
# * {NoSuchBucket}
|
|
33
38
|
# * {NoSuchKey}
|
|
34
39
|
# * {NoSuchUpload}
|
|
35
40
|
# * {ObjectAlreadyInActiveTierError}
|
|
36
41
|
# * {ObjectNotInActiveTierError}
|
|
42
|
+
# * {TooManyParts}
|
|
37
43
|
#
|
|
38
44
|
# Additionally, error classes are dynamically generated for service errors based on the error code
|
|
39
45
|
# if they are not defined above.
|
|
@@ -61,6 +67,66 @@ module Aws::S3
|
|
|
61
67
|
end
|
|
62
68
|
end
|
|
63
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
|
+
|
|
90
|
+
class InvalidObjectState < ServiceError
|
|
91
|
+
|
|
92
|
+
# @param [Seahorse::Client::RequestContext] context
|
|
93
|
+
# @param [String] message
|
|
94
|
+
# @param [Aws::S3::Types::InvalidObjectState] data
|
|
95
|
+
def initialize(context, message, data = Aws::EmptyStructure.new)
|
|
96
|
+
super(context, message, data)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [String]
|
|
100
|
+
def storage_class
|
|
101
|
+
@data[:storage_class]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @return [String]
|
|
105
|
+
def access_tier
|
|
106
|
+
@data[:access_tier]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
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
|
+
|
|
64
130
|
class NoSuchBucket < ServiceError
|
|
65
131
|
|
|
66
132
|
# @param [Seahorse::Client::RequestContext] context
|
|
@@ -111,5 +177,18 @@ module Aws::S3
|
|
|
111
177
|
end
|
|
112
178
|
end
|
|
113
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
|
+
|
|
114
190
|
end
|
|
115
191
|
end
|
|
192
|
+
|
|
193
|
+
# Load customizations if they exist
|
|
194
|
+
require 'aws-sdk-s3/customizations/errors'
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# WARNING ABOUT GENERATED CODE
|
|
4
4
|
#
|
|
5
5
|
# This file is generated. See the contributing guide for more information:
|
|
6
|
-
# https://github.com/aws/aws-sdk-ruby/blob/
|
|
6
|
+
# https://github.com/aws/aws-sdk-ruby/blob/version-3/CONTRIBUTING.md
|
|
7
7
|
#
|
|
8
8
|
# WARNING ABOUT GENERATED CODE
|
|
9
9
|
|
|
@@ -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,141 +1,317 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'pathname'
|
|
4
|
-
require '
|
|
4
|
+
require 'securerandom'
|
|
5
5
|
require 'set'
|
|
6
|
-
require 'tmpdir'
|
|
7
6
|
|
|
8
7
|
module Aws
|
|
9
8
|
module S3
|
|
10
9
|
# @api private
|
|
11
10
|
class FileDownloader
|
|
12
|
-
|
|
13
11
|
MIN_CHUNK_SIZE = 5 * 1024 * 1024
|
|
14
12
|
MAX_PARTS = 10_000
|
|
15
|
-
|
|
13
|
+
HEAD_OPTIONS = Set.new(Client.api.operation(:head_object).input.shape.member_names)
|
|
14
|
+
GET_OPTIONS = Set.new(Client.api.operation(:get_object).input.shape.member_names)
|
|
16
15
|
|
|
17
16
|
def initialize(options = {})
|
|
18
17
|
@client = options[:client] || Client.new
|
|
18
|
+
@executor = options[:executor]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# @return [Client]
|
|
22
22
|
attr_reader :client
|
|
23
23
|
|
|
24
24
|
def download(destination, options = {})
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
validate_destination!(destination)
|
|
26
|
+
opts = build_download_opts(destination, options)
|
|
27
|
+
validate_opts!(opts)
|
|
28
|
+
|
|
29
|
+
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
|
30
|
+
case opts[:mode]
|
|
31
|
+
when 'auto' then multipart_download(opts)
|
|
32
|
+
when 'single_request' then single_request(opts)
|
|
33
|
+
when 'get_range' then range_request(opts)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
File.rename(opts[:temp_path], destination) if opts[:temp_path]
|
|
37
|
+
ensure
|
|
38
|
+
cleanup_temp_file(opts)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def build_download_opts(destination, opts)
|
|
44
|
+
{
|
|
45
|
+
destination: destination,
|
|
46
|
+
mode: opts.delete(:mode) || 'auto',
|
|
47
|
+
chunk_size: opts.delete(:chunk_size),
|
|
48
|
+
on_checksum_validated: opts.delete(:on_checksum_validated),
|
|
49
|
+
progress_callback: opts.delete(:progress_callback),
|
|
50
|
+
params: opts,
|
|
51
|
+
temp_path: nil
|
|
32
52
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def cleanup_temp_file(opts)
|
|
56
|
+
return unless opts
|
|
57
|
+
|
|
58
|
+
temp_file = opts[:temp_path]
|
|
59
|
+
File.delete(temp_file) if temp_file && File.exist?(temp_file)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def download_with_executor(part_list, total_size, opts)
|
|
63
|
+
download_attempts = 0
|
|
64
|
+
completion_queue = Queue.new
|
|
65
|
+
abort_download = false
|
|
66
|
+
error = nil
|
|
67
|
+
progress = MultipartProgress.new(part_list, total_size, opts[:progress_callback])
|
|
68
|
+
|
|
69
|
+
while (part = part_list.shift)
|
|
70
|
+
break if abort_download
|
|
71
|
+
|
|
72
|
+
download_attempts += 1
|
|
73
|
+
@executor.post(part) do |p|
|
|
74
|
+
update_progress(progress, p)
|
|
75
|
+
resp = @client.get_object(p.params)
|
|
76
|
+
range = extract_range(resp.content_range)
|
|
77
|
+
validate_range(range, p.params[:range]) if p.params[:range]
|
|
78
|
+
write(resp.body, range, opts)
|
|
79
|
+
|
|
80
|
+
execute_checksum_callback(resp, opts)
|
|
81
|
+
rescue StandardError => e
|
|
82
|
+
abort_download = true
|
|
83
|
+
error = e
|
|
84
|
+
ensure
|
|
85
|
+
completion_queue << :done
|
|
45
86
|
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
download_attempts.times { completion_queue.pop }
|
|
90
|
+
raise error unless error.nil?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def handle_checksum_mode_option(option_key, opts)
|
|
94
|
+
return false unless option_key == :checksum_mode && opts[:checksum_mode] == 'DISABLED'
|
|
95
|
+
|
|
96
|
+
msg = ':checksum_mode option is deprecated. Checksums will be validated by default. ' \
|
|
97
|
+
'To disable checksum validation, set :response_checksum_validation to "when_required" on your S3 client.'
|
|
98
|
+
warn(msg)
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def get_opts(opts)
|
|
103
|
+
GET_OPTIONS.each_with_object({}) do |k, h|
|
|
104
|
+
next if k == :checksum_mode
|
|
105
|
+
|
|
106
|
+
h[k] = opts[k] if opts.key?(k)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def head_opts(opts)
|
|
111
|
+
HEAD_OPTIONS.each_with_object({}) do |k, h|
|
|
112
|
+
next if handle_checksum_mode_option(k, opts)
|
|
113
|
+
|
|
114
|
+
h[k] = opts[k] if opts.key?(k)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def compute_chunk(chunk_size, file_size)
|
|
119
|
+
raise ArgumentError, ":chunk_size shouldn't exceed total file size." if chunk_size && chunk_size > file_size
|
|
120
|
+
|
|
121
|
+
chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def compute_mode(file_size, total_parts, etag, opts)
|
|
125
|
+
chunk_size = compute_chunk(opts[:chunk_size], file_size)
|
|
126
|
+
part_size = (file_size.to_f / total_parts).ceil
|
|
127
|
+
|
|
128
|
+
resolve_temp_path(opts)
|
|
129
|
+
if chunk_size < part_size
|
|
130
|
+
multithreaded_get_by_ranges(file_size, etag, opts)
|
|
46
131
|
else
|
|
47
|
-
|
|
48
|
-
'mode should be :single_request, :get_range or :auto'
|
|
49
|
-
raise ArgumentError, msg
|
|
132
|
+
multithreaded_get_by_parts(total_parts, file_size, etag, opts)
|
|
50
133
|
end
|
|
51
134
|
end
|
|
52
135
|
|
|
53
|
-
|
|
136
|
+
def extract_range(value)
|
|
137
|
+
value.match(%r{bytes (?<range>\d+-\d+)/\d+})[:range]
|
|
138
|
+
end
|
|
54
139
|
|
|
55
|
-
def multipart_download
|
|
56
|
-
resp = @client.head_object(
|
|
140
|
+
def multipart_download(opts)
|
|
141
|
+
resp = @client.head_object(head_opts(opts[:params].merge(part_number: 1)))
|
|
57
142
|
count = resp.parts_count
|
|
143
|
+
|
|
58
144
|
if count.nil? || count <= 1
|
|
59
|
-
resp.content_length
|
|
60
|
-
single_request
|
|
61
|
-
|
|
145
|
+
if resp.content_length <= MIN_CHUNK_SIZE
|
|
146
|
+
single_request(opts)
|
|
147
|
+
else
|
|
148
|
+
resolve_temp_path(opts)
|
|
149
|
+
multithreaded_get_by_ranges(resp.content_length, resp.etag, opts)
|
|
150
|
+
end
|
|
62
151
|
else
|
|
63
|
-
#
|
|
64
|
-
resp = @client.head_object(
|
|
65
|
-
resp.content_length
|
|
66
|
-
single_request
|
|
67
|
-
|
|
152
|
+
# covers cases when given object is not uploaded via UploadPart API
|
|
153
|
+
resp = @client.head_object(head_opts(opts[:params])) # partNumber is an option
|
|
154
|
+
if resp.content_length <= MIN_CHUNK_SIZE
|
|
155
|
+
single_request(opts)
|
|
156
|
+
else
|
|
157
|
+
compute_mode(resp.content_length, count, resp.etag, opts)
|
|
158
|
+
end
|
|
68
159
|
end
|
|
69
160
|
end
|
|
70
161
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
multithreaded_get_by_ranges(construct_chunks(file_size))
|
|
76
|
-
else
|
|
77
|
-
multithreaded_get_by_parts(count)
|
|
162
|
+
def multithreaded_get_by_parts(total_parts, file_size, etag, opts)
|
|
163
|
+
parts = (1..total_parts).map do |part|
|
|
164
|
+
params = get_opts(opts[:params].merge(part_number: part, if_match: etag))
|
|
165
|
+
Part.new(part_number: part, params: params)
|
|
78
166
|
end
|
|
167
|
+
download_with_executor(PartList.new(parts), file_size, opts)
|
|
79
168
|
end
|
|
80
169
|
|
|
81
|
-
def
|
|
170
|
+
def multithreaded_get_by_ranges(file_size, etag, opts)
|
|
82
171
|
offset = 0
|
|
83
|
-
default_chunk_size = compute_chunk(file_size)
|
|
172
|
+
default_chunk_size = compute_chunk(opts[:chunk_size], file_size)
|
|
84
173
|
chunks = []
|
|
85
|
-
|
|
174
|
+
part_number = 1 # parts start at 1
|
|
175
|
+
while offset < file_size
|
|
86
176
|
progress = offset + default_chunk_size
|
|
87
|
-
|
|
88
|
-
|
|
177
|
+
progress = file_size if progress > file_size
|
|
178
|
+
params = get_opts(opts[:params].merge(range: "bytes=#{offset}-#{progress - 1}", if_match: etag))
|
|
179
|
+
chunks << Part.new(part_number: part_number, size: (progress - offset), params: params)
|
|
180
|
+
part_number += 1
|
|
181
|
+
offset = progress
|
|
89
182
|
end
|
|
90
|
-
chunks
|
|
183
|
+
download_with_executor(PartList.new(chunks), file_size, opts)
|
|
91
184
|
end
|
|
92
185
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
|
|
98
|
-
end
|
|
186
|
+
def range_request(opts)
|
|
187
|
+
resp = @client.head_object(head_opts(opts[:params]))
|
|
188
|
+
resolve_temp_path(opts)
|
|
189
|
+
multithreaded_get_by_ranges(resp.content_length, resp.etag, opts)
|
|
99
190
|
end
|
|
100
191
|
|
|
101
|
-
def
|
|
102
|
-
|
|
103
|
-
|
|
192
|
+
def resolve_temp_path(opts)
|
|
193
|
+
return if [File, Tempfile].include?(opts[:destination].class)
|
|
194
|
+
|
|
195
|
+
opts[:temp_path] ||= "#{opts[:destination]}.s3tmp.#{SecureRandom.alphanumeric(8)}"
|
|
104
196
|
end
|
|
105
197
|
|
|
106
|
-
def
|
|
107
|
-
|
|
198
|
+
def single_request(opts)
|
|
199
|
+
params = get_opts(opts[:params]).merge(response_target: opts[:destination])
|
|
200
|
+
params[:on_chunk_received] = single_part_progress(opts) if opts[:progress_callback]
|
|
201
|
+
resp = @client.get_object(params)
|
|
202
|
+
return resp unless opts[:on_checksum_validated]
|
|
203
|
+
|
|
204
|
+
opts[:on_checksum_validated].call(resp.checksum_validated, resp) if resp.checksum_validated
|
|
205
|
+
resp
|
|
108
206
|
end
|
|
109
207
|
|
|
110
|
-
def
|
|
111
|
-
|
|
208
|
+
def single_part_progress(opts)
|
|
209
|
+
proc do |_chunk, bytes_read, total_size|
|
|
210
|
+
opts[:progress_callback].call([bytes_read], [total_size], total_size)
|
|
211
|
+
end
|
|
112
212
|
end
|
|
113
213
|
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
@params.merge(param.to_sym => chunk)
|
|
121
|
-
)
|
|
122
|
-
write(resp)
|
|
123
|
-
end
|
|
214
|
+
def update_progress(progress, part)
|
|
215
|
+
return unless progress.progress_callback
|
|
216
|
+
|
|
217
|
+
part.params[:on_chunk_received] =
|
|
218
|
+
proc do |_chunk, bytes, total|
|
|
219
|
+
progress.call(part.part_number, bytes, total)
|
|
124
220
|
end
|
|
125
|
-
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def execute_checksum_callback(resp, opts)
|
|
224
|
+
return unless opts[:on_checksum_validated] && resp.checksum_validated
|
|
225
|
+
|
|
226
|
+
opts[:on_checksum_validated].call(resp.checksum_validated, resp)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def validate_destination!(destination)
|
|
230
|
+
valid_types = [String, Pathname, File, Tempfile]
|
|
231
|
+
return if valid_types.include?(destination.class)
|
|
232
|
+
|
|
233
|
+
raise ArgumentError, "Invalid destination, expected #{valid_types.join(', ')} but got: #{destination.class}"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def validate_opts!(opts)
|
|
237
|
+
if opts[:on_checksum_validated] && !opts[:on_checksum_validated].respond_to?(:call)
|
|
238
|
+
raise ArgumentError, ':on_checksum_validated must be callable'
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
valid_modes = %w[auto get_range single_request]
|
|
242
|
+
unless valid_modes.include?(opts[:mode])
|
|
243
|
+
msg = "Invalid mode #{opts[:mode]} provided, :mode should be single_request, get_range or auto"
|
|
244
|
+
raise ArgumentError, msg
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
if opts[:mode] == 'get_range' && opts[:chunk_size].nil?
|
|
248
|
+
raise ArgumentError, 'In get_range mode, :chunk_size must be provided'
|
|
126
249
|
end
|
|
250
|
+
|
|
251
|
+
if opts[:chunk_size] && opts[:chunk_size] <= 0
|
|
252
|
+
raise ArgumentError, ':chunk_size must be positive'
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def validate_range(actual, expected)
|
|
257
|
+
return if actual == expected.match(/bytes=(?<range>\d+-\d+)/)[:range]
|
|
258
|
+
|
|
259
|
+
raise MultipartDownloadError, "multipart download failed: expected range of #{expected} but got #{actual}"
|
|
127
260
|
end
|
|
128
261
|
|
|
129
|
-
def write(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
262
|
+
def write(body, range, opts)
|
|
263
|
+
path = opts[:temp_path] || opts[:destination]
|
|
264
|
+
File.write(path, body.read, range.split('-').first.to_i)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# @api private
|
|
268
|
+
class Part < Struct.new(:part_number, :size, :params)
|
|
269
|
+
include Aws::Structure
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# @api private
|
|
273
|
+
class PartList
|
|
274
|
+
include Enumerable
|
|
275
|
+
def initialize(parts = [])
|
|
276
|
+
@parts = parts
|
|
277
|
+
@mutex = Mutex.new
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def shift
|
|
281
|
+
@mutex.synchronize { @parts.shift }
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def size
|
|
285
|
+
@mutex.synchronize { @parts.size }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def clear!
|
|
289
|
+
@mutex.synchronize { @parts.clear }
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def each(&block)
|
|
293
|
+
@mutex.synchronize { @parts.each(&block) }
|
|
294
|
+
end
|
|
133
295
|
end
|
|
134
296
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
297
|
+
# @api private
|
|
298
|
+
class MultipartProgress
|
|
299
|
+
def initialize(parts, total_size, progress_callback)
|
|
300
|
+
@bytes_received = Array.new(parts.size, 0)
|
|
301
|
+
@part_sizes = parts.map(&:size)
|
|
302
|
+
@total_size = total_size
|
|
303
|
+
@progress_callback = progress_callback
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
attr_reader :progress_callback
|
|
307
|
+
|
|
308
|
+
def call(part_number, bytes_received, total)
|
|
309
|
+
# part numbers start at 1
|
|
310
|
+
@bytes_received[part_number - 1] = bytes_received
|
|
311
|
+
# part size may not be known until we get the first response
|
|
312
|
+
@part_sizes[part_number - 1] ||= total
|
|
313
|
+
@progress_callback.call(@bytes_received, @part_sizes, @total_size)
|
|
314
|
+
end
|
|
139
315
|
end
|
|
140
316
|
end
|
|
141
317
|
end
|