aws-sdk-resources 2.0.1.pre → 2.0.2.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/aws-sdk-resources.rb +6 -1
- data/lib/aws-sdk-resources/resource.rb +6 -5
- data/lib/aws-sdk-resources/s3.rb +41 -0
- data/lib/aws-sdk-resources/s3/file_part.rb +74 -0
- data/lib/aws-sdk-resources/s3/file_uploader.rb +58 -0
- data/lib/aws-sdk-resources/s3/multipart_file_uploader.rb +163 -0
- data/lib/aws-sdk-resources/s3/multipart_upload_error.rb +16 -0
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8d73ea1b21204559a8fb036e60f6d380268a743
|
4
|
+
data.tar.gz: b92f963d53c46d4dc64aaab930a5acea6bcba3b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf73777ea642f80159f357fc2b0e6e1c935637c7688e650ba8949205a242517997a88e84527d02c78264caf37abf19cc92e69f3b510d42a5fca945fc9ff4dca0
|
7
|
+
data.tar.gz: ad3dd237c4c1ef3068794bfa1d2ec347f07e44880321a748f610f18037aa82cd57203984d808e431f8106ed4844ea61626cf1b78bd020eaf87da4158d1bb558d
|
data/lib/aws-sdk-resources.rb
CHANGED
@@ -4,11 +4,10 @@ module Aws
|
|
4
4
|
|
5
5
|
extend OperationMethods
|
6
6
|
|
7
|
-
# @overload initialize(options = {})
|
8
7
|
# @overload initialize(*identifiers, options = {})
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
8
|
+
# @param [Hash] options Options except `:data` and identifier options are
|
9
|
+
# used to construct a {Client} unless `:client` is given.
|
10
|
+
# @option options [Client] :client
|
12
11
|
def initialize(*args)
|
13
12
|
options = args.last.is_a?(Hash) ? args.pop.dup : {}
|
14
13
|
@identifiers = extract_identifiers(args, options)
|
@@ -62,7 +61,9 @@ module Aws
|
|
62
61
|
if options[:client]
|
63
62
|
options[:client]
|
64
63
|
else
|
65
|
-
self.class.client_class.new(options
|
64
|
+
self.class.client_class.new(options.merge(
|
65
|
+
user_agent_suffix: "resources"
|
66
|
+
))
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
|
4
|
+
autoload :FilePart, 'aws-sdk-resources/s3/file_part'
|
5
|
+
autoload :FileUploader, 'aws-sdk-resources/s3/file_uploader'
|
6
|
+
autoload :MultipartFileUploader, 'aws-sdk-resources/s3/multipart_file_uploader'
|
7
|
+
autoload :MultipartUploadError, 'aws-sdk-resources/s3/multipart_upload_error'
|
8
|
+
|
9
|
+
class Object
|
10
|
+
|
11
|
+
# Uploads a file from disk to the current object in S3.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
#
|
15
|
+
# obj.upload_file('/path/to/file')
|
16
|
+
#
|
17
|
+
# @param [String,Pathname,File,Tempfile] source A file or path to a file
|
18
|
+
# on the local file system that should be uploaded to this object.
|
19
|
+
#
|
20
|
+
# @option options [Integer] :multipart_threshold (15728640) Files larger
|
21
|
+
# than `:multipart_threshold` are uploaded using the S3 multipart APIs.
|
22
|
+
# Default threshold is 15MB.
|
23
|
+
#
|
24
|
+
# @raise [MultipartUploadError] If an object is being uploaded in
|
25
|
+
# parts, and the upload can not be completed, then the upload is
|
26
|
+
# aborted and this error is raised. The raised error has a `#errors`
|
27
|
+
# method that returns the failures that caused the upload to be
|
28
|
+
# aborted.
|
29
|
+
#
|
30
|
+
# @return [void]
|
31
|
+
#
|
32
|
+
def upload_file(source, options = {})
|
33
|
+
uploader = FileUploader.new(
|
34
|
+
multipart_threshold: options.delete(:multipart_threshold),
|
35
|
+
client: client)
|
36
|
+
uploader.upload(source, options.merge(bucket: bucket_name, key: key))
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
|
4
|
+
# A utility class that provides an IO-like interface to a portion of
|
5
|
+
# a file on disk.
|
6
|
+
# @api private
|
7
|
+
class FilePart
|
8
|
+
|
9
|
+
# @option options [required,String,Pathname,File,Tempfile] :source
|
10
|
+
# @option options [required,Integer] :offset The file part will read
|
11
|
+
# starting at this byte offset.
|
12
|
+
# @option options [required,Integer] :size The maximum number of bytes to
|
13
|
+
# read from the `:offset`.
|
14
|
+
def initialize(options = {})
|
15
|
+
@source = options[:source]
|
16
|
+
@first_byte = options[:offset]
|
17
|
+
@last_byte = @first_byte + options[:size]
|
18
|
+
@size = options[:size]
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String,Pathname,File,Tempfile]
|
22
|
+
attr_reader :source
|
23
|
+
|
24
|
+
# @return [Integer]
|
25
|
+
attr_reader :first_byte
|
26
|
+
|
27
|
+
# @return [Integer]
|
28
|
+
attr_reader :last_byte
|
29
|
+
|
30
|
+
# @return [Integer]
|
31
|
+
attr_reader :size
|
32
|
+
|
33
|
+
def read(bytes = nil, output_buffer = nil)
|
34
|
+
open_file unless @file
|
35
|
+
read_from_file(bytes, output_buffer)
|
36
|
+
end
|
37
|
+
|
38
|
+
def rewind
|
39
|
+
if @file
|
40
|
+
@file.seek(@first_byte)
|
41
|
+
@position = @first_byte
|
42
|
+
end
|
43
|
+
0
|
44
|
+
end
|
45
|
+
|
46
|
+
def close
|
47
|
+
@file.close if @file
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def open_file
|
53
|
+
@file = File.open(@source, 'rb')
|
54
|
+
rewind
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_from_file(bytes, output_buffer)
|
58
|
+
if bytes
|
59
|
+
data = @file.read([remaining_bytes, bytes].min)
|
60
|
+
data = nil if data == ''
|
61
|
+
else
|
62
|
+
data = @file.read(remaining_bytes)
|
63
|
+
end
|
64
|
+
@position += data ? data.bytesize : 0
|
65
|
+
output_buffer ? output_buffer.replace(data || '') : data
|
66
|
+
end
|
67
|
+
|
68
|
+
def remaining_bytes
|
69
|
+
@last_byte - @position
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
# @api private
|
6
|
+
class FileUploader
|
7
|
+
|
8
|
+
FIFTEEN_MEGABYTES = 15 * 1024 * 1024
|
9
|
+
|
10
|
+
# @option options [Client] :client
|
11
|
+
# @option options [Integer] :multipart_threshold Files greater than
|
12
|
+
# `:multipart_threshold` bytes are uploaded using S3 multipart APIs.
|
13
|
+
def initialize(options = {})
|
14
|
+
@options = options
|
15
|
+
@client = options[:client] || Client.new
|
16
|
+
@multipart_threshold = options[:multipart_threshold] || FIFTEEN_MEGABYTES
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Client]
|
20
|
+
attr_reader :client
|
21
|
+
|
22
|
+
# @return [Integer] Files larger than this in bytes are uploaded
|
23
|
+
# using a {MultipartFileUploader}.
|
24
|
+
attr_reader :multipart_threshold
|
25
|
+
|
26
|
+
# @param [String,Pathname,File,Tempfile] source
|
27
|
+
# @option options [requried,String] :bucket
|
28
|
+
# @option options [requried,String] :key
|
29
|
+
# @return [void]
|
30
|
+
def upload(source, options = {})
|
31
|
+
if File.size(source) >= multipart_threshold
|
32
|
+
MultipartFileUploader.new(@options).upload(source, options)
|
33
|
+
else
|
34
|
+
put_object(source, options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def put_object(source, options)
|
41
|
+
open_file(source) do |file|
|
42
|
+
@client.put_object(options.merge(body: file))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def open_file(source)
|
47
|
+
if String === source || Pathname === source
|
48
|
+
file = File.open(source, 'rb')
|
49
|
+
yield(file)
|
50
|
+
file.close
|
51
|
+
else
|
52
|
+
yield(source)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Aws
|
5
|
+
module S3
|
6
|
+
# @api private
|
7
|
+
class MultipartFileUploader
|
8
|
+
|
9
|
+
MIN_PART_SIZE = 5 * 1024 * 1024 # 5MB
|
10
|
+
|
11
|
+
FILE_TOO_SMALL = "unable to multipart upload files smaller than 5MB"
|
12
|
+
|
13
|
+
MAX_PARTS = 10_000
|
14
|
+
|
15
|
+
THREAD_COUNT = 10
|
16
|
+
|
17
|
+
# @option options [Client] :client
|
18
|
+
def initialize(options = {})
|
19
|
+
@client = options[:client] || Client.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Client]
|
23
|
+
attr_reader :client
|
24
|
+
|
25
|
+
# @param [String,Pathname,File,Tempfile] source
|
26
|
+
# @option options [requried,String] :bucket
|
27
|
+
# @option options [requried,String] :key
|
28
|
+
# @return [void]
|
29
|
+
def upload(source, options = {})
|
30
|
+
if File.size(source) < MIN_PART_SIZE
|
31
|
+
raise ArgumentError, FILE_TOO_SMALL
|
32
|
+
else
|
33
|
+
upload_id = initiate_upload(options)
|
34
|
+
parts = upload_parts(upload_id, source, options)
|
35
|
+
complete_upload(upload_id, parts, options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def initiate_upload(options)
|
42
|
+
@client.create_multipart_upload(options).upload_id
|
43
|
+
end
|
44
|
+
|
45
|
+
def complete_upload(upload_id, parts, options)
|
46
|
+
@client.complete_multipart_upload(
|
47
|
+
bucket: options[:bucket],
|
48
|
+
key: options[:key],
|
49
|
+
upload_id: upload_id,
|
50
|
+
multipart_upload: { parts: parts })
|
51
|
+
end
|
52
|
+
|
53
|
+
def upload_parts(upload_id, source, options)
|
54
|
+
pending = PartList.new(compute_parts(upload_id, source, options))
|
55
|
+
completed = PartList.new
|
56
|
+
errors = upload_in_threads(pending, completed)
|
57
|
+
if errors.empty?
|
58
|
+
completed.to_a.sort_by { |part| part[:part_number] }
|
59
|
+
else
|
60
|
+
abort_upload(upload_id, options, errors)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def abort_upload(upload_id, options, errors)
|
65
|
+
@client.abort_multipart_upload(
|
66
|
+
bucket: options[:bucket],
|
67
|
+
key: options[:key],
|
68
|
+
upload_id: upload_id
|
69
|
+
)
|
70
|
+
msg = "multipart upload failed: #{errors.map(&:message).join("; ")}"
|
71
|
+
raise MultipartUploadError.new(msg, errors)
|
72
|
+
rescue MultipartUploadError => error
|
73
|
+
raise error
|
74
|
+
rescue => error
|
75
|
+
msg = "failed to abort multipart upload: #{error.message}"
|
76
|
+
raise MultipartUploadError.new(msg, errors + [error])
|
77
|
+
end
|
78
|
+
|
79
|
+
def compute_parts(upload_id, source, options)
|
80
|
+
size = File.size(source)
|
81
|
+
default_part_size = compute_default_part_size(size)
|
82
|
+
offset = 0
|
83
|
+
part_number = 1
|
84
|
+
parts = []
|
85
|
+
while offset < size
|
86
|
+
parts << options.merge(
|
87
|
+
upload_id: upload_id,
|
88
|
+
part_number: part_number,
|
89
|
+
body: FilePart.new(
|
90
|
+
source: source,
|
91
|
+
offset: offset,
|
92
|
+
size: part_size(size, default_part_size, offset)
|
93
|
+
)
|
94
|
+
)
|
95
|
+
part_number += 1
|
96
|
+
offset += default_part_size
|
97
|
+
end
|
98
|
+
parts
|
99
|
+
end
|
100
|
+
|
101
|
+
def upload_in_threads(pending, completed)
|
102
|
+
threads = []
|
103
|
+
THREAD_COUNT.times do
|
104
|
+
thread = Thread.new do
|
105
|
+
begin
|
106
|
+
while part = pending.shift
|
107
|
+
resp = @client.upload_part(part)
|
108
|
+
part[:body].close
|
109
|
+
completed.push(etag: resp.etag, part_number: part[:part_number])
|
110
|
+
end
|
111
|
+
nil
|
112
|
+
rescue => error
|
113
|
+
# keep other threads from uploading other parts
|
114
|
+
pending.clear!
|
115
|
+
error
|
116
|
+
end
|
117
|
+
end
|
118
|
+
thread.abort_on_exception = true
|
119
|
+
threads << thread
|
120
|
+
end
|
121
|
+
errors = threads.map(&:value).compact
|
122
|
+
end
|
123
|
+
|
124
|
+
def compute_default_part_size(source_size)
|
125
|
+
[(source_size.to_f / MAX_PARTS).ceil, MIN_PART_SIZE].max.to_i
|
126
|
+
end
|
127
|
+
|
128
|
+
def part_size(total_size, part_size, offset)
|
129
|
+
if offset + part_size > total_size
|
130
|
+
total_size - offset
|
131
|
+
else
|
132
|
+
part_size
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# @api private
|
137
|
+
class PartList
|
138
|
+
|
139
|
+
def initialize(parts = [])
|
140
|
+
@parts = parts
|
141
|
+
@mutex = Mutex.new
|
142
|
+
end
|
143
|
+
|
144
|
+
def push(part)
|
145
|
+
@mutex.synchronize { @parts.push(part) }
|
146
|
+
end
|
147
|
+
|
148
|
+
def shift
|
149
|
+
@mutex.synchronize { @parts.shift }
|
150
|
+
end
|
151
|
+
|
152
|
+
def clear!
|
153
|
+
@mutex.synchronize { @parts.clear }
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_a
|
157
|
+
@mutex.synchronize { @parts.dup }
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
class MultipartUploadError < StandardError
|
4
|
+
|
5
|
+
def initialize(message, errors)
|
6
|
+
@errors = errors
|
7
|
+
super(message)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Array<StandardError>] The list of errors encountered
|
11
|
+
# when uploading or aborting the upload.
|
12
|
+
attr_reader :errors
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aws-sdk-resources
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.2.pre
|
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: 2014-
|
11
|
+
date: 2014-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-core
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.0.
|
19
|
+
version: 2.0.2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.0.
|
26
|
+
version: 2.0.2
|
27
27
|
description: Provides resource-oriented abstractions for AWS.
|
28
28
|
email:
|
29
29
|
executables: []
|
@@ -51,6 +51,11 @@ files:
|
|
51
51
|
- lib/aws-sdk-resources/request.rb
|
52
52
|
- lib/aws-sdk-resources/request_params.rb
|
53
53
|
- lib/aws-sdk-resources/resource.rb
|
54
|
+
- lib/aws-sdk-resources/s3/file_part.rb
|
55
|
+
- lib/aws-sdk-resources/s3/file_uploader.rb
|
56
|
+
- lib/aws-sdk-resources/s3/multipart_file_uploader.rb
|
57
|
+
- lib/aws-sdk-resources/s3/multipart_upload_error.rb
|
58
|
+
- lib/aws-sdk-resources/s3.rb
|
54
59
|
- lib/aws-sdk-resources/source.rb
|
55
60
|
- lib/aws-sdk-resources/validator/context.rb
|
56
61
|
- lib/aws-sdk-resources/validator/identifier_validator.rb
|
@@ -84,4 +89,3 @@ signing_key:
|
|
84
89
|
specification_version: 4
|
85
90
|
summary: AWS SDK for Ruby - Resources
|
86
91
|
test_files: []
|
87
|
-
has_rdoc:
|