aws-sdk-s3 1.191.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 +4 -4
- data/CHANGELOG.md +114 -0
- data/VERSION +1 -1
- data/lib/aws-sdk-s3/bucket.rb +33 -1
- data/lib/aws-sdk-s3/bucket_acl.rb +1 -1
- data/lib/aws-sdk-s3/bucket_versioning.rb +33 -0
- data/lib/aws-sdk-s3/client.rb +1503 -392
- data/lib/aws-sdk-s3/client_api.rb +248 -0
- data/lib/aws-sdk-s3/customizations/object.rb +76 -86
- data/lib/aws-sdk-s3/customizations.rb +4 -1
- data/lib/aws-sdk-s3/default_executor.rb +103 -0
- data/lib/aws-sdk-s3/encryption/client.rb +2 -2
- 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 +2 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +98 -23
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -162
- data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +17 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +8 -0
- 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 +17 -17
- data/lib/aws-sdk-s3/endpoint_provider.rb +220 -50
- data/lib/aws-sdk-s3/endpoints.rb +96 -0
- data/lib/aws-sdk-s3/file_downloader.rb +197 -134
- data/lib/aws-sdk-s3/file_uploader.rb +9 -13
- data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +92 -107
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +96 -107
- data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
- data/lib/aws-sdk-s3/object.rb +56 -27
- data/lib/aws-sdk-s3/object_acl.rb +1 -1
- data/lib/aws-sdk-s3/object_summary.rb +42 -13
- data/lib/aws-sdk-s3/object_version.rb +7 -9
- data/lib/aws-sdk-s3/plugins/endpoints.rb +1 -1
- data/lib/aws-sdk-s3/resource.rb +6 -0
- data/lib/aws-sdk-s3/transfer_manager.rb +303 -0
- data/lib/aws-sdk-s3/types.rb +1179 -260
- data/lib/aws-sdk-s3.rb +1 -1
- data/sig/bucket.rbs +11 -2
- data/sig/client.rbs +127 -13
- data/sig/multipart_upload.rbs +1 -1
- data/sig/object.rbs +7 -5
- data/sig/object_summary.rbs +7 -5
- data/sig/resource.rbs +8 -1
- data/sig/types.rbs +173 -14
- metadata +22 -3
data/lib/aws-sdk-s3/endpoints.rb
CHANGED
|
@@ -63,6 +63,18 @@ module Aws::S3
|
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
class CreateBucketMetadataConfiguration
|
|
67
|
+
def self.build(context)
|
|
68
|
+
Aws::S3::EndpointParameters.create(
|
|
69
|
+
context.config,
|
|
70
|
+
bucket: context.params[:bucket],
|
|
71
|
+
use_dual_stack: context[:use_dualstack_endpoint],
|
|
72
|
+
accelerate: context[:use_accelerate_endpoint],
|
|
73
|
+
use_s3_express_control_endpoint: true,
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
66
78
|
class CreateBucketMetadataTableConfiguration
|
|
67
79
|
def self.build(context)
|
|
68
80
|
Aws::S3::EndpointParameters.create(
|
|
@@ -183,6 +195,18 @@ module Aws::S3
|
|
|
183
195
|
end
|
|
184
196
|
end
|
|
185
197
|
|
|
198
|
+
class DeleteBucketMetadataConfiguration
|
|
199
|
+
def self.build(context)
|
|
200
|
+
Aws::S3::EndpointParameters.create(
|
|
201
|
+
context.config,
|
|
202
|
+
bucket: context.params[:bucket],
|
|
203
|
+
use_dual_stack: context[:use_dualstack_endpoint],
|
|
204
|
+
accelerate: context[:use_accelerate_endpoint],
|
|
205
|
+
use_s3_express_control_endpoint: true,
|
|
206
|
+
)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
186
210
|
class DeleteBucketMetadataTableConfiguration
|
|
187
211
|
def self.build(context)
|
|
188
212
|
Aws::S3::EndpointParameters.create(
|
|
@@ -313,6 +337,17 @@ module Aws::S3
|
|
|
313
337
|
end
|
|
314
338
|
end
|
|
315
339
|
|
|
340
|
+
class GetBucketAbac
|
|
341
|
+
def self.build(context)
|
|
342
|
+
Aws::S3::EndpointParameters.create(
|
|
343
|
+
context.config,
|
|
344
|
+
bucket: context.params[:bucket],
|
|
345
|
+
use_dual_stack: context[:use_dualstack_endpoint],
|
|
346
|
+
accelerate: context[:use_accelerate_endpoint],
|
|
347
|
+
)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
316
351
|
class GetBucketAccelerateConfiguration
|
|
317
352
|
def self.build(context)
|
|
318
353
|
Aws::S3::EndpointParameters.create(
|
|
@@ -445,6 +480,18 @@ module Aws::S3
|
|
|
445
480
|
end
|
|
446
481
|
end
|
|
447
482
|
|
|
483
|
+
class GetBucketMetadataConfiguration
|
|
484
|
+
def self.build(context)
|
|
485
|
+
Aws::S3::EndpointParameters.create(
|
|
486
|
+
context.config,
|
|
487
|
+
bucket: context.params[:bucket],
|
|
488
|
+
use_dual_stack: context[:use_dualstack_endpoint],
|
|
489
|
+
accelerate: context[:use_accelerate_endpoint],
|
|
490
|
+
use_s3_express_control_endpoint: true,
|
|
491
|
+
)
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
448
495
|
class GetBucketMetadataTableConfiguration
|
|
449
496
|
def self.build(context)
|
|
450
497
|
Aws::S3::EndpointParameters.create(
|
|
@@ -842,6 +889,17 @@ module Aws::S3
|
|
|
842
889
|
end
|
|
843
890
|
end
|
|
844
891
|
|
|
892
|
+
class PutBucketAbac
|
|
893
|
+
def self.build(context)
|
|
894
|
+
Aws::S3::EndpointParameters.create(
|
|
895
|
+
context.config,
|
|
896
|
+
bucket: context.params[:bucket],
|
|
897
|
+
use_dual_stack: context[:use_dualstack_endpoint],
|
|
898
|
+
accelerate: context[:use_accelerate_endpoint],
|
|
899
|
+
)
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
|
|
845
903
|
class PutBucketAccelerateConfiguration
|
|
846
904
|
def self.build(context)
|
|
847
905
|
Aws::S3::EndpointParameters.create(
|
|
@@ -1196,6 +1254,30 @@ module Aws::S3
|
|
|
1196
1254
|
end
|
|
1197
1255
|
end
|
|
1198
1256
|
|
|
1257
|
+
class UpdateBucketMetadataInventoryTableConfiguration
|
|
1258
|
+
def self.build(context)
|
|
1259
|
+
Aws::S3::EndpointParameters.create(
|
|
1260
|
+
context.config,
|
|
1261
|
+
bucket: context.params[:bucket],
|
|
1262
|
+
use_dual_stack: context[:use_dualstack_endpoint],
|
|
1263
|
+
accelerate: context[:use_accelerate_endpoint],
|
|
1264
|
+
use_s3_express_control_endpoint: true,
|
|
1265
|
+
)
|
|
1266
|
+
end
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
class UpdateBucketMetadataJournalTableConfiguration
|
|
1270
|
+
def self.build(context)
|
|
1271
|
+
Aws::S3::EndpointParameters.create(
|
|
1272
|
+
context.config,
|
|
1273
|
+
bucket: context.params[:bucket],
|
|
1274
|
+
use_dual_stack: context[:use_dualstack_endpoint],
|
|
1275
|
+
accelerate: context[:use_accelerate_endpoint],
|
|
1276
|
+
use_s3_express_control_endpoint: true,
|
|
1277
|
+
)
|
|
1278
|
+
end
|
|
1279
|
+
end
|
|
1280
|
+
|
|
1199
1281
|
class UploadPart
|
|
1200
1282
|
def self.build(context)
|
|
1201
1283
|
Aws::S3::EndpointParameters.create(
|
|
@@ -1242,6 +1324,8 @@ module Aws::S3
|
|
|
1242
1324
|
CopyObject.build(context)
|
|
1243
1325
|
when :create_bucket
|
|
1244
1326
|
CreateBucket.build(context)
|
|
1327
|
+
when :create_bucket_metadata_configuration
|
|
1328
|
+
CreateBucketMetadataConfiguration.build(context)
|
|
1245
1329
|
when :create_bucket_metadata_table_configuration
|
|
1246
1330
|
CreateBucketMetadataTableConfiguration.build(context)
|
|
1247
1331
|
when :create_multipart_upload
|
|
@@ -1262,6 +1346,8 @@ module Aws::S3
|
|
|
1262
1346
|
DeleteBucketInventoryConfiguration.build(context)
|
|
1263
1347
|
when :delete_bucket_lifecycle
|
|
1264
1348
|
DeleteBucketLifecycle.build(context)
|
|
1349
|
+
when :delete_bucket_metadata_configuration
|
|
1350
|
+
DeleteBucketMetadataConfiguration.build(context)
|
|
1265
1351
|
when :delete_bucket_metadata_table_configuration
|
|
1266
1352
|
DeleteBucketMetadataTableConfiguration.build(context)
|
|
1267
1353
|
when :delete_bucket_metrics_configuration
|
|
@@ -1284,6 +1370,8 @@ module Aws::S3
|
|
|
1284
1370
|
DeleteObjects.build(context)
|
|
1285
1371
|
when :delete_public_access_block
|
|
1286
1372
|
DeletePublicAccessBlock.build(context)
|
|
1373
|
+
when :get_bucket_abac
|
|
1374
|
+
GetBucketAbac.build(context)
|
|
1287
1375
|
when :get_bucket_accelerate_configuration
|
|
1288
1376
|
GetBucketAccelerateConfiguration.build(context)
|
|
1289
1377
|
when :get_bucket_acl
|
|
@@ -1306,6 +1394,8 @@ module Aws::S3
|
|
|
1306
1394
|
GetBucketLocation.build(context)
|
|
1307
1395
|
when :get_bucket_logging
|
|
1308
1396
|
GetBucketLogging.build(context)
|
|
1397
|
+
when :get_bucket_metadata_configuration
|
|
1398
|
+
GetBucketMetadataConfiguration.build(context)
|
|
1309
1399
|
when :get_bucket_metadata_table_configuration
|
|
1310
1400
|
GetBucketMetadataTableConfiguration.build(context)
|
|
1311
1401
|
when :get_bucket_metrics_configuration
|
|
@@ -1374,6 +1464,8 @@ module Aws::S3
|
|
|
1374
1464
|
ListObjectsV2.build(context)
|
|
1375
1465
|
when :list_parts
|
|
1376
1466
|
ListParts.build(context)
|
|
1467
|
+
when :put_bucket_abac
|
|
1468
|
+
PutBucketAbac.build(context)
|
|
1377
1469
|
when :put_bucket_accelerate_configuration
|
|
1378
1470
|
PutBucketAccelerateConfiguration.build(context)
|
|
1379
1471
|
when :put_bucket_acl
|
|
@@ -1434,6 +1526,10 @@ module Aws::S3
|
|
|
1434
1526
|
RestoreObject.build(context)
|
|
1435
1527
|
when :select_object_content
|
|
1436
1528
|
SelectObjectContent.build(context)
|
|
1529
|
+
when :update_bucket_metadata_inventory_table_configuration
|
|
1530
|
+
UpdateBucketMetadataInventoryTableConfiguration.build(context)
|
|
1531
|
+
when :update_bucket_metadata_journal_table_configuration
|
|
1532
|
+
UpdateBucketMetadataJournalTableConfiguration.build(context)
|
|
1437
1533
|
when :upload_part
|
|
1438
1534
|
UploadPart.build(context)
|
|
1439
1535
|
when :upload_part_copy
|
|
@@ -1,209 +1,270 @@
|
|
|
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
|
-
@chunk_size = options[:chunk_size]
|
|
29
|
-
@params = {
|
|
30
|
-
bucket: options[:bucket],
|
|
31
|
-
key: options[:key]
|
|
32
|
-
}
|
|
33
|
-
@params[:version_id] = options[:version_id] if options[:version_id]
|
|
34
|
-
@on_checksum_validated = options[:on_checksum_validated]
|
|
35
|
-
@progress_callback = options[:progress_callback]
|
|
36
|
-
|
|
37
|
-
validate!
|
|
25
|
+
validate_destination!(destination)
|
|
26
|
+
opts = build_download_opts(destination, options)
|
|
27
|
+
validate_opts!(opts)
|
|
38
28
|
|
|
39
29
|
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
|
40
|
-
case
|
|
41
|
-
when 'auto' then multipart_download
|
|
42
|
-
when 'single_request' then single_request
|
|
43
|
-
when 'get_range'
|
|
44
|
-
if @chunk_size
|
|
45
|
-
resp = @client.head_object(@params)
|
|
46
|
-
multithreaded_get_by_ranges(resp.content_length, resp.etag)
|
|
47
|
-
else
|
|
48
|
-
msg = 'In :get_range mode, :chunk_size must be provided'
|
|
49
|
-
raise ArgumentError, msg
|
|
50
|
-
end
|
|
51
|
-
else
|
|
52
|
-
msg = "Invalid mode #{@mode} provided, "\
|
|
53
|
-
'mode should be :single_request, :get_range or :auto'
|
|
54
|
-
raise ArgumentError, msg
|
|
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)
|
|
55
34
|
end
|
|
56
35
|
end
|
|
36
|
+
File.rename(opts[:temp_path], destination) if opts[:temp_path]
|
|
37
|
+
ensure
|
|
38
|
+
cleanup_temp_file(opts)
|
|
57
39
|
end
|
|
58
40
|
|
|
59
41
|
private
|
|
60
42
|
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
|
|
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)
|
|
64
107
|
end
|
|
65
108
|
end
|
|
66
109
|
|
|
67
|
-
def
|
|
68
|
-
|
|
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)))
|
|
69
142
|
count = resp.parts_count
|
|
143
|
+
|
|
70
144
|
if count.nil? || count <= 1
|
|
71
145
|
if resp.content_length <= MIN_CHUNK_SIZE
|
|
72
|
-
single_request
|
|
146
|
+
single_request(opts)
|
|
73
147
|
else
|
|
74
|
-
|
|
148
|
+
resolve_temp_path(opts)
|
|
149
|
+
multithreaded_get_by_ranges(resp.content_length, resp.etag, opts)
|
|
75
150
|
end
|
|
76
151
|
else
|
|
77
|
-
#
|
|
78
|
-
resp = @client.head_object(
|
|
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
|
|
79
154
|
if resp.content_length <= MIN_CHUNK_SIZE
|
|
80
|
-
single_request
|
|
155
|
+
single_request(opts)
|
|
81
156
|
else
|
|
82
|
-
compute_mode(resp.content_length, count, resp.etag)
|
|
157
|
+
compute_mode(resp.content_length, count, resp.etag, opts)
|
|
83
158
|
end
|
|
84
159
|
end
|
|
85
160
|
end
|
|
86
161
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
multithreaded_get_by_ranges(file_size, etag)
|
|
92
|
-
else
|
|
93
|
-
multithreaded_get_by_parts(count, file_size, etag)
|
|
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)
|
|
94
166
|
end
|
|
167
|
+
download_with_executor(PartList.new(parts), file_size, opts)
|
|
95
168
|
end
|
|
96
169
|
|
|
97
|
-
def
|
|
170
|
+
def multithreaded_get_by_ranges(file_size, etag, opts)
|
|
98
171
|
offset = 0
|
|
99
|
-
default_chunk_size = compute_chunk(file_size)
|
|
172
|
+
default_chunk_size = compute_chunk(opts[:chunk_size], file_size)
|
|
100
173
|
chunks = []
|
|
174
|
+
part_number = 1 # parts start at 1
|
|
101
175
|
while offset < file_size
|
|
102
176
|
progress = offset + default_chunk_size
|
|
103
177
|
progress = file_size if progress > file_size
|
|
104
|
-
|
|
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
|
|
105
181
|
offset = progress
|
|
106
182
|
end
|
|
107
|
-
chunks
|
|
183
|
+
download_with_executor(PartList.new(chunks), file_size, opts)
|
|
108
184
|
end
|
|
109
185
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@chunk_size || [
|
|
115
|
-
(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE
|
|
116
|
-
].max.to_i
|
|
117
|
-
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)
|
|
118
190
|
end
|
|
119
191
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
|
|
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)}"
|
|
123
196
|
end
|
|
124
197
|
|
|
125
|
-
def
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
part_number += 1
|
|
140
|
-
offset = progress
|
|
141
|
-
end
|
|
142
|
-
download_in_threads(PartList.new(chunks), file_size)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def multithreaded_get_by_parts(n_parts, total_size, etag)
|
|
146
|
-
parts = (1..n_parts).map do |part|
|
|
147
|
-
Part.new(part_number: part, params: @params.merge(part_number: part, if_match: etag))
|
|
148
|
-
end
|
|
149
|
-
download_in_threads(PartList.new(parts), total_size)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def download_in_threads(pending, total_size)
|
|
153
|
-
threads = []
|
|
154
|
-
progress = MultipartProgress.new(pending, total_size, @progress_callback) if @progress_callback
|
|
155
|
-
@thread_count.times do
|
|
156
|
-
thread = Thread.new do
|
|
157
|
-
begin
|
|
158
|
-
while part = pending.shift
|
|
159
|
-
if progress
|
|
160
|
-
part.params[:on_chunk_received] =
|
|
161
|
-
proc do |_chunk, bytes, total|
|
|
162
|
-
progress.call(part.part_number, bytes, total)
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
resp = @client.get_object(part.params)
|
|
166
|
-
write(resp)
|
|
167
|
-
if @on_checksum_validated && resp.checksum_validated
|
|
168
|
-
@on_checksum_validated.call(resp.checksum_validated, resp)
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
nil
|
|
172
|
-
rescue => error
|
|
173
|
-
# keep other threads from downloading other parts
|
|
174
|
-
pending.clear!
|
|
175
|
-
raise error
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
threads << thread
|
|
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
|
|
206
|
+
end
|
|
207
|
+
|
|
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)
|
|
179
211
|
end
|
|
180
|
-
threads.map(&:value).compact
|
|
181
212
|
end
|
|
182
213
|
|
|
183
|
-
def
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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)
|
|
220
|
+
end
|
|
187
221
|
end
|
|
188
222
|
|
|
189
|
-
def
|
|
190
|
-
|
|
191
|
-
params[:on_chunk_received] = single_part_progress if @progress_callback
|
|
192
|
-
resp = @client.get_object(params)
|
|
223
|
+
def execute_checksum_callback(resp, opts)
|
|
224
|
+
return unless opts[:on_checksum_validated] && resp.checksum_validated
|
|
193
225
|
|
|
194
|
-
|
|
226
|
+
opts[:on_checksum_validated].call(resp.checksum_validated, resp)
|
|
227
|
+
end
|
|
195
228
|
|
|
196
|
-
|
|
229
|
+
def validate_destination!(destination)
|
|
230
|
+
valid_types = [String, Pathname, File, Tempfile]
|
|
231
|
+
return if valid_types.include?(destination.class)
|
|
197
232
|
|
|
198
|
-
|
|
233
|
+
raise ArgumentError, "Invalid destination, expected #{valid_types.join(', ')} but got: #{destination.class}"
|
|
199
234
|
end
|
|
200
235
|
|
|
201
|
-
def
|
|
202
|
-
|
|
203
|
-
|
|
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'
|
|
204
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}"
|
|
205
260
|
end
|
|
206
261
|
|
|
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
|
|
207
268
|
class Part < Struct.new(:part_number, :size, :params)
|
|
208
269
|
include Aws::Structure
|
|
209
270
|
end
|
|
@@ -242,6 +303,8 @@ module Aws
|
|
|
242
303
|
@progress_callback = progress_callback
|
|
243
304
|
end
|
|
244
305
|
|
|
306
|
+
attr_reader :progress_callback
|
|
307
|
+
|
|
245
308
|
def call(part_number, bytes_received, total)
|
|
246
309
|
# part numbers start at 1
|
|
247
310
|
@bytes_received[part_number - 1] = bytes_received
|
|
@@ -7,23 +7,21 @@ 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
|
|
14
14
|
# @option options [Integer] :multipart_threshold (104857600)
|
|
15
15
|
def initialize(options = {})
|
|
16
|
-
@options = options
|
|
17
16
|
@client = options[:client] || Client.new
|
|
18
|
-
@
|
|
19
|
-
|
|
17
|
+
@executor = options[:executor]
|
|
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.
|
|
@@ -38,11 +36,9 @@ module Aws
|
|
|
38
36
|
# @return [void]
|
|
39
37
|
def upload(source, options = {})
|
|
40
38
|
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
|
41
|
-
if File.size(source) >= multipart_threshold
|
|
42
|
-
MultipartFileUploader.new(@
|
|
39
|
+
if File.size(source) >= @multipart_threshold
|
|
40
|
+
MultipartFileUploader.new(client: @client, executor: @executor).upload(source, options)
|
|
43
41
|
else
|
|
44
|
-
# remove multipart parameters not supported by put_object
|
|
45
|
-
options.delete(:thread_count)
|
|
46
42
|
put_object(source, options)
|
|
47
43
|
end
|
|
48
44
|
end
|
|
@@ -50,9 +46,9 @@ module Aws
|
|
|
50
46
|
|
|
51
47
|
private
|
|
52
48
|
|
|
53
|
-
def open_file(source)
|
|
54
|
-
if String
|
|
55
|
-
File.open(source, 'rb'
|
|
49
|
+
def open_file(source, &block)
|
|
50
|
+
if source.is_a?(String) || source.is_a?(Pathname)
|
|
51
|
+
File.open(source, 'rb', &block)
|
|
56
52
|
else
|
|
57
53
|
yield(source)
|
|
58
54
|
end
|