aws-sdk-s3 1.10.0 → 1.208.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 +5 -5
- data/CHANGELOG.md +1517 -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 +1062 -99
- data/lib/aws-sdk-s3/bucket_acl.rb +67 -17
- data/lib/aws-sdk-s3/bucket_cors.rb +80 -17
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +71 -19
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +126 -20
- data/lib/aws-sdk-s3/bucket_logging.rb +68 -18
- data/lib/aws-sdk-s3/bucket_notification.rb +56 -20
- data/lib/aws-sdk-s3/bucket_policy.rb +108 -17
- data/lib/aws-sdk-s3/bucket_region_cache.rb +11 -5
- data/lib/aws-sdk-s3/bucket_request_payment.rb +60 -15
- data/lib/aws-sdk-s3/bucket_tagging.rb +71 -17
- data/lib/aws-sdk-s3/bucket_versioning.rb +166 -17
- data/lib/aws-sdk-s3/bucket_website.rb +78 -17
- data/lib/aws-sdk-s3/client.rb +20068 -3879
- data/lib/aws-sdk-s3/client_api.rb +1957 -209
- data/lib/aws-sdk-s3/customizations/bucket.rb +57 -38
- data/lib/aws-sdk-s3/customizations/errors.rb +40 -0
- data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
- data/lib/aws-sdk-s3/customizations/object.rb +338 -68
- data/lib/aws-sdk-s3/customizations/object_summary.rb +17 -0
- data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
- data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
- data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
- data/lib/aws-sdk-s3/customizations.rb +30 -27
- data/lib/aws-sdk-s3/default_executor.rb +103 -0
- data/lib/aws-sdk-s3/encryption/client.rb +29 -8
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +45 -5
- data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +15 -2
- data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
- data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +11 -3
- data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
- data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +48 -11
- data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
- data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
- data/lib/aws-sdk-s3/encryption.rb +4 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +645 -0
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +68 -0
- data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +187 -0
- data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +67 -0
- data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
- data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +75 -0
- data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +181 -0
- data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +108 -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 +24 -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 +886 -0
- data/lib/aws-sdk-s3/endpoints.rb +1544 -0
- data/lib/aws-sdk-s3/errors.rb +181 -1
- data/lib/aws-sdk-s3/event_streams.rb +69 -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 +261 -82
- data/lib/aws-sdk-s3/file_part.rb +16 -13
- data/lib/aws-sdk-s3/file_uploader.rb +37 -22
- data/lib/aws-sdk-s3/legacy_signer.rb +19 -26
- data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +142 -80
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +191 -0
- data/lib/aws-sdk-s3/multipart_upload.rb +342 -31
- data/lib/aws-sdk-s3/multipart_upload_error.rb +5 -4
- data/lib/aws-sdk-s3/multipart_upload_part.rb +387 -47
- data/lib/aws-sdk-s3/object.rb +2733 -204
- data/lib/aws-sdk-s3/object_acl.rb +112 -25
- data/lib/aws-sdk-s3/object_copier.rb +9 -5
- data/lib/aws-sdk-s3/object_multipart_copier.rb +50 -23
- data/lib/aws-sdk-s3/object_summary.rb +2265 -181
- data/lib/aws-sdk-s3/object_version.rb +542 -74
- data/lib/aws-sdk-s3/plugins/accelerate.rb +17 -64
- 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 +7 -43
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +20 -3
- data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
- data/lib/aws-sdk-s3/plugins/dualstack.rb +7 -50
- data/lib/aws-sdk-s3/plugins/endpoints.rb +86 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +5 -4
- data/lib/aws-sdk-s3/plugins/express_session_auth.rb +88 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +62 -17
- data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +44 -0
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +5 -1
- data/lib/aws-sdk-s3/plugins/md5s.rb +14 -67
- data/lib/aws-sdk-s3/plugins/redirects.rb +5 -1
- data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +67 -93
- data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +137 -0
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +4 -1
- data/lib/aws-sdk-s3/presigned_post.rb +160 -99
- data/lib/aws-sdk-s3/presigner.rb +178 -81
- data/lib/aws-sdk-s3/resource.rb +164 -15
- data/lib/aws-sdk-s3/transfer_manager.rb +303 -0
- data/lib/aws-sdk-s3/types.rb +15981 -4168
- data/lib/aws-sdk-s3/waiters.rb +67 -1
- data/lib/aws-sdk-s3.rb +46 -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 +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 +97 -14
|
@@ -1,138 +1,317 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'pathname'
|
|
2
|
-
require '
|
|
4
|
+
require 'securerandom'
|
|
3
5
|
require 'set'
|
|
4
|
-
require 'tmpdir'
|
|
5
6
|
|
|
6
7
|
module Aws
|
|
7
8
|
module S3
|
|
8
9
|
# @api private
|
|
9
10
|
class FileDownloader
|
|
10
|
-
|
|
11
11
|
MIN_CHUNK_SIZE = 5 * 1024 * 1024
|
|
12
12
|
MAX_PARTS = 10_000
|
|
13
|
-
|
|
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)
|
|
14
15
|
|
|
15
16
|
def initialize(options = {})
|
|
16
17
|
@client = options[:client] || Client.new
|
|
18
|
+
@executor = options[:executor]
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
# @return [Client]
|
|
20
22
|
attr_reader :client
|
|
21
23
|
|
|
22
24
|
def download(destination, options = {})
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
when "single_request" then single_request
|
|
33
|
-
when "get_range"
|
|
34
|
-
if @chunk_size
|
|
35
|
-
resp = @client.head_object(bucket: @bucket, key: @key)
|
|
36
|
-
multithreaded_get_by_ranges(construct_chunks(resp.content_length))
|
|
37
|
-
else
|
|
38
|
-
msg = "In :get_range mode, :chunk_size must be provided"
|
|
39
|
-
raise ArgumentError, msg
|
|
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)
|
|
40
34
|
end
|
|
41
|
-
else
|
|
42
|
-
msg = "Invalid mode #{@mode} provided, "\
|
|
43
|
-
"mode should be :single_request, :get_range or :auto"
|
|
44
|
-
raise ArgumentError, msg
|
|
45
35
|
end
|
|
36
|
+
File.rename(opts[:temp_path], destination) if opts[:temp_path]
|
|
37
|
+
ensure
|
|
38
|
+
cleanup_temp_file(opts)
|
|
46
39
|
end
|
|
47
40
|
|
|
48
41
|
private
|
|
49
42
|
|
|
50
|
-
def
|
|
51
|
-
|
|
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
|
|
52
|
+
}
|
|
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
|
|
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)
|
|
131
|
+
else
|
|
132
|
+
multithreaded_get_by_parts(total_parts, file_size, etag, opts)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def extract_range(value)
|
|
137
|
+
value.match(%r{bytes (?<range>\d+-\d+)/\d+})[:range]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def multipart_download(opts)
|
|
141
|
+
resp = @client.head_object(head_opts(opts[:params].merge(part_number: 1)))
|
|
52
142
|
count = resp.parts_count
|
|
143
|
+
|
|
53
144
|
if count.nil? || count <= 1
|
|
54
|
-
resp.content_length
|
|
55
|
-
single_request
|
|
56
|
-
|
|
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
|
|
57
151
|
else
|
|
58
|
-
#
|
|
59
|
-
resp = @client.head_object(
|
|
60
|
-
resp.content_length
|
|
61
|
-
single_request
|
|
62
|
-
|
|
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
|
|
63
159
|
end
|
|
64
160
|
end
|
|
65
161
|
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
multithreaded_get_by_ranges(construct_chunks(file_size))
|
|
71
|
-
else
|
|
72
|
-
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)
|
|
73
166
|
end
|
|
167
|
+
download_with_executor(PartList.new(parts), file_size, opts)
|
|
74
168
|
end
|
|
75
169
|
|
|
76
|
-
def
|
|
170
|
+
def multithreaded_get_by_ranges(file_size, etag, opts)
|
|
77
171
|
offset = 0
|
|
78
|
-
default_chunk_size = compute_chunk(file_size)
|
|
172
|
+
default_chunk_size = compute_chunk(opts[:chunk_size], file_size)
|
|
79
173
|
chunks = []
|
|
80
|
-
|
|
174
|
+
part_number = 1 # parts start at 1
|
|
175
|
+
while offset < file_size
|
|
81
176
|
progress = offset + default_chunk_size
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
84
182
|
end
|
|
85
|
-
chunks
|
|
183
|
+
download_with_executor(PartList.new(chunks), file_size, opts)
|
|
86
184
|
end
|
|
87
185
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
|
|
93
|
-
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)
|
|
94
190
|
end
|
|
95
191
|
|
|
96
|
-
def
|
|
97
|
-
|
|
98
|
-
|
|
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)}"
|
|
99
196
|
end
|
|
100
197
|
|
|
101
|
-
def
|
|
102
|
-
|
|
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
|
|
103
206
|
end
|
|
104
207
|
|
|
105
|
-
def
|
|
106
|
-
|
|
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
|
|
107
212
|
end
|
|
108
213
|
|
|
109
|
-
def
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
:bucket => @bucket,
|
|
116
|
-
:key => @key,
|
|
117
|
-
param.to_sym => chunk
|
|
118
|
-
)
|
|
119
|
-
write(resp)
|
|
120
|
-
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)
|
|
121
220
|
end
|
|
122
|
-
|
|
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'
|
|
123
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}"
|
|
124
260
|
end
|
|
125
261
|
|
|
126
|
-
def write(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
130
295
|
end
|
|
131
296
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
315
|
end
|
|
137
316
|
end
|
|
138
317
|
end
|
data/lib/aws-sdk-s3/file_part.rb
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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]
|
|
@@ -56,14 +61,12 @@ module Aws
|
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
def read_from_file(bytes, output_buffer)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
else
|
|
63
|
-
data = @file.read(remaining_bytes)
|
|
64
|
-
end
|
|
64
|
+
length = [remaining_bytes, *bytes].min
|
|
65
|
+
data = @file.read(length, output_buffer)
|
|
66
|
+
|
|
65
67
|
@position += data ? data.bytesize : 0
|
|
66
|
-
|
|
68
|
+
|
|
69
|
+
data.to_s unless bytes && (data.nil? || data.empty?)
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
def remaining_bytes
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'pathname'
|
|
2
4
|
|
|
3
5
|
module Aws
|
|
@@ -5,54 +7,67 @@ module Aws
|
|
|
5
7
|
# @api private
|
|
6
8
|
class FileUploader
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
DEFAULT_MULTIPART_THRESHOLD = 100 * 1024 * 1024
|
|
9
11
|
|
|
12
|
+
# @param [Hash] options
|
|
10
13
|
# @option options [Client] :client
|
|
11
|
-
# @option options [Integer] :multipart_threshold
|
|
12
|
-
# `:multipart_threshold` bytes are uploaded using S3 multipart APIs.
|
|
14
|
+
# @option options [Integer] :multipart_threshold (104857600)
|
|
13
15
|
def initialize(options = {})
|
|
14
|
-
@options = options
|
|
15
16
|
@client = options[:client] || Client.new
|
|
16
|
-
@
|
|
17
|
+
@executor = options[:executor]
|
|
18
|
+
@multipart_threshold = options[:multipart_threshold] || DEFAULT_MULTIPART_THRESHOLD
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
# @return [Client]
|
|
20
22
|
attr_reader :client
|
|
21
23
|
|
|
22
|
-
# @return [Integer] Files larger than this in bytes are uploaded
|
|
23
|
-
# using a {MultipartFileUploader}.
|
|
24
|
+
# @return [Integer] Files larger than or equal to this in bytes are uploaded using a {MultipartFileUploader}.
|
|
24
25
|
attr_reader :multipart_threshold
|
|
25
26
|
|
|
26
|
-
# @param [String,Pathname,File,Tempfile] source
|
|
27
|
-
# @option options [required,String] :bucket
|
|
28
|
-
# @option options [required,String] :key
|
|
27
|
+
# @param [String, Pathname, File, Tempfile] source The file to upload.
|
|
28
|
+
# @option options [required, String] :bucket The bucket to upload to.
|
|
29
|
+
# @option options [required, String] :key The key for the object.
|
|
30
|
+
# @option options [Proc] :progress_callback
|
|
31
|
+
# A Proc that will be called when each chunk of the upload is sent.
|
|
32
|
+
# It will be invoked with [bytes_read], [total_sizes]
|
|
33
|
+
# @option options [Integer] :thread_count
|
|
34
|
+
# The thread count to use for multipart uploads. Ignored for
|
|
35
|
+
# objects smaller than the multipart threshold.
|
|
29
36
|
# @return [void]
|
|
30
37
|
def upload(source, options = {})
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
|
39
|
+
if File.size(source) >= @multipart_threshold
|
|
40
|
+
MultipartFileUploader.new(client: @client, executor: @executor).upload(source, options)
|
|
41
|
+
else
|
|
42
|
+
put_object(source, options)
|
|
43
|
+
end
|
|
35
44
|
end
|
|
36
45
|
end
|
|
37
46
|
|
|
38
47
|
private
|
|
39
48
|
|
|
49
|
+
def open_file(source, &block)
|
|
50
|
+
if source.is_a?(String) || source.is_a?(Pathname)
|
|
51
|
+
File.open(source, 'rb', &block)
|
|
52
|
+
else
|
|
53
|
+
yield(source)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
40
57
|
def put_object(source, options)
|
|
58
|
+
if (callback = options.delete(:progress_callback))
|
|
59
|
+
options[:on_chunk_sent] = single_part_progress(callback)
|
|
60
|
+
end
|
|
41
61
|
open_file(source) do |file|
|
|
42
62
|
@client.put_object(options.merge(body: file))
|
|
43
63
|
end
|
|
44
64
|
end
|
|
45
65
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
yield(file)
|
|
50
|
-
file.close
|
|
51
|
-
else
|
|
52
|
-
yield(source)
|
|
66
|
+
def single_part_progress(progress_callback)
|
|
67
|
+
proc do |_chunk, bytes_read, total_size|
|
|
68
|
+
progress_callback.call([bytes_read], [total_size])
|
|
53
69
|
end
|
|
54
70
|
end
|
|
55
|
-
|
|
56
71
|
end
|
|
57
72
|
end
|
|
58
73
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'set'
|
|
2
4
|
require 'time'
|
|
3
5
|
require 'openssl'
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
+
require "cgi/escape"
|
|
7
|
+
require "cgi/util" if RUBY_VERSION < "3.5"
|
|
6
8
|
require 'aws-sdk-core/query'
|
|
7
9
|
|
|
8
10
|
module Aws
|
|
@@ -155,33 +157,24 @@ module Aws
|
|
|
155
157
|
end
|
|
156
158
|
|
|
157
159
|
def uri_escape(s)
|
|
158
|
-
|
|
159
160
|
#URI.escape(s)
|
|
160
161
|
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
# (
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
# URI.escape(s),
|
|
177
|
-
# ]
|
|
178
|
-
# next if e.uniq.length == 1
|
|
179
|
-
# puts("%5s %5s %5s %5s %5s %5s %5s" % ([s.inspect] + e))
|
|
180
|
-
# }
|
|
181
|
-
#
|
|
182
|
-
WEBrick::HTTPUtils.escape(s).gsub('%5B', '[').gsub('%5D', ']')
|
|
162
|
+
# (0..255).each {|c|
|
|
163
|
+
# s = [c].pack("C")
|
|
164
|
+
# e = [
|
|
165
|
+
# CGI.escape(s),
|
|
166
|
+
# ERB::Util.url_encode(s),
|
|
167
|
+
# URI.encode_www_form_component(s),
|
|
168
|
+
# WEBrick::HTTPUtils.escape_form(s),
|
|
169
|
+
# WEBrick::HTTPUtils.escape(s),
|
|
170
|
+
# URI.escape(s),
|
|
171
|
+
# URI::DEFAULT_PARSER.escape(s)
|
|
172
|
+
# ]
|
|
173
|
+
# next if e.uniq.length == 1
|
|
174
|
+
# puts("%5s %5s %5s %5s %5s %5s %5s %5s" % ([s.inspect] + e))
|
|
175
|
+
# }
|
|
176
|
+
URI::DEFAULT_PARSER.escape(s)
|
|
183
177
|
end
|
|
184
|
-
|
|
185
178
|
end
|
|
186
179
|
end
|
|
187
180
|
end
|