aws-sdk-s3 1.13.0 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76fdd52e31b770c550908db83818c5842b0e704c
4
- data.tar.gz: 596f4b30103399a4bfe7b8cf2cad093cfd8ac6e1
3
+ metadata.gz: ebf985aabc5276f0257be4b38c436b5261e133aa
4
+ data.tar.gz: eb740e2166050fd5b48daf78d95797c167fc5d5b
5
5
  SHA512:
6
- metadata.gz: 4a3f32fa2257bb3dd5bdfd94b329a54a047e1fa6b6ccb1425e21b7e86c9664dd1d62165d400a65dbc6c814fa0935c08f2f3e9d7535cf7576094e4b332c745405
7
- data.tar.gz: 7e3729ceec9fe3cd819926dd14ba0fdf675af8610773f5a0daf5766fbd5bcfb6242bd4b91e1910dbb04c17732004bb46d8bd1976f22181d4625a95a3d1bf5381
6
+ metadata.gz: 5de1c034a92b407852bd6b268d42c3ecb8593f0d1c27ea0056e6b83f09263f42a4e8bfdfa3df18f01f0435bf1206a6f779c7d34e3c23d36a3e21510345a0da1e
7
+ data.tar.gz: 4a6d12d6dd43abb6064fd45b8ce703c98f46ffa51d220cd2b67a7f35e4ac1bcf00c0876874359f623db56b2f6ad95ff355b8669e967830bdd912a23868a7268f
@@ -63,6 +63,6 @@ require_relative 'aws-sdk-s3/event_streams'
63
63
  # @service
64
64
  module Aws::S3
65
65
 
66
- GEM_VERSION = '1.13.0'
66
+ GEM_VERSION = '1.14.0'
67
67
 
68
68
  end
@@ -6371,7 +6371,7 @@ module Aws::S3
6371
6371
  params: params,
6372
6372
  config: config)
6373
6373
  context[:gem_name] = 'aws-sdk-s3'
6374
- context[:gem_version] = '1.13.0'
6374
+ context[:gem_version] = '1.14.0'
6375
6375
  Seahorse::Client::Request.new(handlers, context)
6376
6376
  end
6377
6377
 
@@ -6,6 +6,7 @@ require 'aws-sdk-s3/file_uploader'
6
6
  require 'aws-sdk-s3/file_downloader'
7
7
  require 'aws-sdk-s3/legacy_signer'
8
8
  require 'aws-sdk-s3/multipart_file_uploader'
9
+ require 'aws-sdk-s3/multipart_stream_uploader'
9
10
  require 'aws-sdk-s3/multipart_upload_error'
10
11
  require 'aws-sdk-s3/object_copier'
11
12
  require 'aws-sdk-s3/object_multipart_copier'
@@ -214,6 +214,54 @@ module Aws
214
214
  url.to_s
215
215
  end
216
216
 
217
+ # Uploads a stream in a streaming fashion to the current object in S3.
218
+ #
219
+ # # Passed chunks automatically split into multipart upload parts
220
+ # # and the parts are uploaded in parallel. This allows for streaming uploads
221
+ # # that never touch the disk.
222
+ #
223
+ # Note that this is known to have issues in JRuby until jruby-9.1.15.0, so avoid using this with older versions of JRuby.
224
+ #
225
+ # @example Streaming chunks of data
226
+ # obj.upload_file do |write_stream|
227
+ # 10.times { write_stream << 'hello' }
228
+ # end
229
+ #
230
+ # @option options [Integer] :thread_count
231
+ # The number of parallel multipart uploads
232
+ # Default `:thread_count` is `10`.
233
+ #
234
+ # @option options [Boolean] :tempfile
235
+ # Normally read data is stored in memory when building the parts in order to complete
236
+ # the underlying multipart upload. By passing `:tempfile => true` data read will be
237
+ # temporarily stored on disk reducing the memory footprint vastly.
238
+ # Default `:tempfile` is `false`.
239
+ #
240
+ # @option options [Integer] :part_size
241
+ # Define how big each part size but the last should be.
242
+ # Default `:part_size` is `5 * 1024 * 1024`.
243
+ #
244
+ # @raise [MultipartUploadError] If an object is being uploaded in
245
+ # parts, and the upload can not be completed, then the upload is
246
+ # aborted and this error is raised. The raised error has a `#errors`
247
+ # method that returns the failures that caused the upload to be
248
+ # aborted.
249
+ #
250
+ # @return [Boolean] Returns `true` when the object is uploaded
251
+ # without any errors.
252
+ #
253
+ def upload_stream(options = {}, &block)
254
+ uploading_options = options.dup
255
+ uploader = MultipartStreamUploader.new(
256
+ client: client,
257
+ thread_count: uploading_options.delete(:thread_count),
258
+ tempfile: uploading_options.delete(:tempfile),
259
+ part_size: uploading_options.delete(:part_size),
260
+ )
261
+ uploader.upload(uploading_options.merge(bucket: bucket_name, key: key), &block)
262
+ true
263
+ end
264
+
217
265
  # Uploads a file from disk to the current object in S3.
218
266
  #
219
267
  # # small files are uploaded in a single API call
@@ -60,6 +60,13 @@ module Aws
60
60
  object.upload_file(source, options)
61
61
  end
62
62
 
63
+ # @options (see Object#upload_stream)
64
+ # @return (see Object#upload_stream)
65
+ # @see Object#upload_stream
66
+ def upload_stream(options = {}, &block)
67
+ object.upload_stream(options, &block)
68
+ end
69
+
63
70
  # @param (see Object#download_file)
64
71
  # @options (see Object#download_file)
65
72
  # @return (see Object#download_file)
@@ -0,0 +1,160 @@
1
+ require 'thread'
2
+ require 'set'
3
+ require 'tempfile'
4
+ require 'stringio'
5
+
6
+ module Aws
7
+ module S3
8
+ # @api private
9
+ class MultipartStreamUploader
10
+ # api private
11
+ PART_SIZE = 5 * 1024 * 1024 # 5MB
12
+
13
+ # api private
14
+ THREAD_COUNT = 10
15
+
16
+ # api private
17
+ TEMPFILE_PREIX = 'aws-sdk-s3-upload_stream'.freeze
18
+
19
+ # @api private
20
+ CREATE_OPTIONS =
21
+ Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
22
+
23
+ # @api private
24
+ UPLOAD_PART_OPTIONS =
25
+ Set.new(Client.api.operation(:upload_part).input.shape.member_names)
26
+
27
+ # @option options [Client] :client
28
+ def initialize(options = {})
29
+ @client = options[:client] || Client.new
30
+ @tempfile = options[:tempfile]
31
+ @part_size = options[:part_size] || PART_SIZE
32
+ @thread_count = options[:thread_count] || THREAD_COUNT
33
+ end
34
+
35
+ # @return [Client]
36
+ attr_reader :client
37
+
38
+ # @option options [required,String] :bucket
39
+ # @option options [required,String] :key
40
+ # @return [void]
41
+ def upload(options = {}, &block)
42
+ upload_id = initiate_upload(options)
43
+ parts = upload_parts(upload_id, options, &block)
44
+ complete_upload(upload_id, parts, options)
45
+ end
46
+
47
+ private
48
+
49
+ def initiate_upload(options)
50
+ @client.create_multipart_upload(create_opts(options)).upload_id
51
+ end
52
+
53
+ def complete_upload(upload_id, parts, options)
54
+ @client.complete_multipart_upload(
55
+ bucket: options[:bucket],
56
+ key: options[:key],
57
+ upload_id: upload_id,
58
+ multipart_upload: { parts: parts })
59
+ end
60
+
61
+ def upload_parts(upload_id, options, &block)
62
+ completed = Queue.new
63
+ errors = IO.pipe do |read_pipe, write_pipe|
64
+ threads = upload_in_threads(read_pipe, completed, upload_part_opts(options).merge(upload_id: upload_id))
65
+ block.call(write_pipe)
66
+ write_pipe.close
67
+ threads.map(&:value).compact
68
+ end
69
+ if errors.empty?
70
+ Array.new(completed.size) { completed.pop }.sort_by { |part| part[:part_number] }
71
+ else
72
+ abort_upload(upload_id, options, errors)
73
+ end
74
+ end
75
+
76
+ def abort_upload(upload_id, options, errors)
77
+ @client.abort_multipart_upload(
78
+ bucket: options[:bucket],
79
+ key: options[:key],
80
+ upload_id: upload_id
81
+ )
82
+ msg = "multipart upload failed: #{errors.map(&:message).join("; ")}"
83
+ raise MultipartUploadError.new(msg, errors)
84
+ rescue MultipartUploadError => error
85
+ raise error
86
+ rescue => error
87
+ msg = "failed to abort multipart upload: #{error.message}"
88
+ raise MultipartUploadError.new(msg, errors + [error])
89
+ end
90
+
91
+ def create_opts(options)
92
+ CREATE_OPTIONS.inject({}) do |hash, key|
93
+ hash[key] = options[key] if options.key?(key)
94
+ hash
95
+ end
96
+ end
97
+
98
+ def upload_part_opts(options)
99
+ UPLOAD_PART_OPTIONS.inject({}) do |hash, key|
100
+ hash[key] = options[key] if options.key?(key)
101
+ hash
102
+ end
103
+ end
104
+
105
+ def read_to_part_body(read_pipe)
106
+ return if read_pipe.closed?
107
+ temp_io = @tempfile ? Tempfile.new(TEMPFILE_PREIX) : StringIO.new
108
+ temp_io.binmode
109
+ bytes_copied = IO.copy_stream(read_pipe, temp_io, @part_size)
110
+ temp_io.rewind
111
+ if bytes_copied == 0
112
+ if Tempfile === temp_io
113
+ temp_io.close
114
+ temp_io.unlink
115
+ end
116
+ nil
117
+ else
118
+ temp_io
119
+ end
120
+ end
121
+
122
+ def upload_in_threads(read_pipe, completed, options)
123
+ mutex = Mutex.new
124
+ part_number = 0
125
+ @thread_count.times.map do
126
+ thread = Thread.new do
127
+ begin
128
+ loop do
129
+ body, thread_part_number = mutex.synchronize do
130
+ [read_to_part_body(read_pipe), part_number += 1]
131
+ end
132
+ break unless body
133
+ begin
134
+ part = options.merge(
135
+ body: body,
136
+ part_number: thread_part_number,
137
+ )
138
+ resp = @client.upload_part(part)
139
+ completed << {etag: resp.etag, part_number: part[:part_number]}
140
+ ensure
141
+ if Tempfile === body
142
+ body.close
143
+ body.unlink
144
+ end
145
+ end
146
+ end
147
+ nil
148
+ rescue => error
149
+ # keep other threads from uploading other parts
150
+ mutex.synchronize { read_pipe.close_read }
151
+ error
152
+ end
153
+ end
154
+ thread.abort_on_exception = true
155
+ thread
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-sdk-s3
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.0
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amazon Web Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-22 00:00:00.000000000 Z
11
+ date: 2018-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-kms
@@ -109,6 +109,7 @@ files:
109
109
  - lib/aws-sdk-s3/file_uploader.rb
110
110
  - lib/aws-sdk-s3/legacy_signer.rb
111
111
  - lib/aws-sdk-s3/multipart_file_uploader.rb
112
+ - lib/aws-sdk-s3/multipart_stream_uploader.rb
112
113
  - lib/aws-sdk-s3/multipart_upload.rb
113
114
  - lib/aws-sdk-s3/multipart_upload_error.rb
114
115
  - lib/aws-sdk-s3/multipart_upload_part.rb