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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +450 -1
- data/VERSION +1 -1
- 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 +720 -128
- data/lib/aws-sdk-s3/bucket_acl.rb +18 -17
- data/lib/aws-sdk-s3/bucket_cors.rb +22 -21
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +23 -18
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +76 -19
- data/lib/aws-sdk-s3/bucket_logging.rb +21 -14
- data/lib/aws-sdk-s3/bucket_notification.rb +6 -6
- data/lib/aws-sdk-s3/bucket_policy.rb +65 -20
- data/lib/aws-sdk-s3/bucket_region_cache.rb +9 -5
- data/lib/aws-sdk-s3/bucket_request_payment.rb +15 -15
- data/lib/aws-sdk-s3/bucket_tagging.rb +19 -19
- data/lib/aws-sdk-s3/bucket_versioning.rb +41 -41
- data/lib/aws-sdk-s3/bucket_website.rb +19 -19
- data/lib/aws-sdk-s3/client.rb +9352 -3264
- data/lib/aws-sdk-s3/client_api.rb +697 -164
- data/lib/aws-sdk-s3/customizations/bucket.rb +1 -1
- data/lib/aws-sdk-s3/customizations/errors.rb +16 -3
- data/lib/aws-sdk-s3/customizations/object.rb +112 -56
- 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.rb +26 -31
- data/lib/aws-sdk-s3/encryption/client.rb +2 -2
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +2 -2
- data/lib/aws-sdk-s3/encryptionV2/client.rb +2 -2
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +2 -2
- data/lib/aws-sdk-s3/endpoint_parameters.rb +54 -15
- data/lib/aws-sdk-s3/endpoint_provider.rb +439 -456
- data/lib/aws-sdk-s3/endpoints.rb +629 -1261
- data/lib/aws-sdk-s3/errors.rb +58 -0
- 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 +156 -69
- data/lib/aws-sdk-s3/file_uploader.rb +4 -6
- data/lib/aws-sdk-s3/legacy_signer.rb +2 -1
- data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +56 -69
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +84 -91
- data/lib/aws-sdk-s3/multipart_upload.rb +179 -26
- data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
- data/lib/aws-sdk-s3/multipart_upload_part.rb +201 -60
- data/lib/aws-sdk-s3/object.rb +2007 -281
- data/lib/aws-sdk-s3/object_acl.rb +43 -23
- data/lib/aws-sdk-s3/object_copier.rb +1 -1
- data/lib/aws-sdk-s3/object_multipart_copier.rb +44 -25
- data/lib/aws-sdk-s3/object_summary.rb +1735 -232
- data/lib/aws-sdk-s3/object_version.rb +394 -52
- data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
- data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
- data/lib/aws-sdk-s3/plugins/endpoints.rb +32 -208
- data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +55 -18
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +3 -1
- data/lib/aws-sdk-s3/plugins/md5s.rb +10 -70
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +7 -2
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +5 -7
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
- data/lib/aws-sdk-s3/presigned_post.rb +52 -43
- data/lib/aws-sdk-s3/presigner.rb +9 -7
- data/lib/aws-sdk-s3/resource.rb +127 -22
- data/lib/aws-sdk-s3/transfer_manager.rb +252 -0
- data/lib/aws-sdk-s3/types.rb +8068 -1887
- data/lib/aws-sdk-s3.rb +35 -31
- 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 +2584 -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 +462 -0
- data/sig/object_acl.rbs +86 -0
- data/sig/object_summary.rbs +345 -0
- data/sig/object_version.rbs +143 -0
- data/sig/resource.rbs +141 -0
- data/sig/types.rbs +2866 -0
- data/sig/waiters.rbs +95 -0
- metadata +44 -12
- data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +0 -31
data/lib/aws-sdk-s3/errors.rb
CHANGED
@@ -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 '
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@
|
34
|
-
|
35
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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(
|
71
|
+
multithreaded_get_by_ranges(resp.content_length, resp.etag)
|
65
72
|
end
|
66
73
|
else
|
67
|
-
#
|
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
|
86
|
+
part_size = (file_size.to_f / count).ceil
|
80
87
|
if chunk_size < part_size
|
81
|
-
multithreaded_get_by_ranges(
|
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
|
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
|
-
|
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
|
101
|
-
|
102
|
-
|
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
|
111
|
-
|
112
|
-
|
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
|
116
|
-
|
158
|
+
def extract_range(value)
|
159
|
+
value.match(%r{bytes (?<range>\d+-\d+)/\d+})[:range]
|
117
160
|
end
|
118
161
|
|
119
|
-
def
|
120
|
-
|
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
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
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.
|
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
|