aws-sdk-resources 2.0.1.pre → 2.0.2.pre

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56e7beb4eb1355b479f0a15f4c1a6c39e041b6f3
4
- data.tar.gz: f84dd430391e20dba1493278bfadc5f375e2846f
3
+ metadata.gz: d8d73ea1b21204559a8fb036e60f6d380268a743
4
+ data.tar.gz: b92f963d53c46d4dc64aaab930a5acea6bcba3b3
5
5
  SHA512:
6
- metadata.gz: b6386e64a750440869b42e43da646ef0ef1d6498a1d90ab1a8c8964a1e211a6d805bf253517b67b63d2a7716cdb8bee9cb36f2351320cd0aa1abbcb266de6ad9
7
- data.tar.gz: 24f80ab77c37d0003aacc7f778a04f8ca993a5b507b1df73eef1c83359b1eafb2c490dc78654fce9a1986654a6355cd2f8ead129b92302e82d8ceec60d9e00a7
6
+ metadata.gz: cf73777ea642f80159f357fc2b0e6e1c935637c7688e650ba8949205a242517997a88e84527d02c78264caf37abf19cc92e69f3b510d42a5fca945fc9ff4dca0
7
+ data.tar.gz: ad3dd237c4c1ef3068794bfa1d2ec347f07e44880321a748f610f18037aa82cd57203984d808e431f8106ed4844ea61626cf1b78bd020eaf87da4158d1bb558d
@@ -31,6 +31,11 @@ module Aws
31
31
  else raise ArgumentError, "invalid resource definition #{definition}"
32
32
  end
33
33
  definition.apply(svc_module)
34
- end
35
34
 
35
+ begin
36
+ require "aws-sdk-resources/#{_.downcase}"
37
+ rescue LoadError
38
+ end
39
+
40
+ end
36
41
  end
@@ -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
- # @param options Options except `:data` and identifier options are
10
- # used to construct a {Client} unless `:client` is given.
11
- # @option options [Client] :client
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.1.pre
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-09-29 00:00:00.000000000 Z
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.1
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.1
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: