isaacfeliu-aws-s3 0.4.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.
@@ -0,0 +1,69 @@
1
+ module AWS
2
+ module S3
3
+ # Anything you do that makes a request to S3 could result in an error. If it does, the AWS::S3 library will raise an exception
4
+ # specific to the error. All exception that are raised as a result of a request returning an error response inherit from the
5
+ # ResponseError exception. So should you choose to rescue any such exception, you can simple rescue ResponseError.
6
+ #
7
+ # Say you go to delete a bucket, but the bucket turns out to not be empty. This results in a BucketNotEmpty error (one of the many
8
+ # errors listed at http://docs.amazonwebservices.com/AmazonS3/2006-03-01/ErrorCodeList.html):
9
+ #
10
+ # begin
11
+ # Bucket.delete('jukebox')
12
+ # rescue ResponseError => error
13
+ # # ...
14
+ # end
15
+ #
16
+ # Once you've captured the exception, you can extract the error message from S3, as well as the full error response, which includes
17
+ # things like the HTTP response code:
18
+ #
19
+ # error
20
+ # # => #<AWS::S3::BucketNotEmpty The bucket you tried to delete is not empty>
21
+ # error.message
22
+ # # => "The bucket you tried to delete is not empty"
23
+ # error.response.code
24
+ # # => 409
25
+ #
26
+ # You could use this information to redisplay the error in a way you see fit, or just to log the error and continue on.
27
+ class Error
28
+ #:stopdoc:
29
+ attr_accessor :response
30
+ def initialize(error, response = nil)
31
+ @error = error
32
+ @response = response
33
+ @container = AWS::S3
34
+ find_or_create_exception!
35
+ end
36
+
37
+ def raise
38
+ Kernel.raise exception.new(message, response)
39
+ end
40
+
41
+ private
42
+ attr_reader :error, :exception, :container
43
+
44
+ def find_or_create_exception!
45
+ @exception = container.const_defined?(code) ? find_exception : create_exception
46
+ end
47
+
48
+ def find_exception
49
+ exception_class = container.const_get(code)
50
+ Kernel.raise ExceptionClassClash.new(exception_class) unless exception_class.ancestors.include?(ResponseError)
51
+ exception_class
52
+ end
53
+
54
+ def create_exception
55
+ container.const_set(code, Class.new(ResponseError))
56
+ end
57
+
58
+ def method_missing(method, *args, &block)
59
+ # We actually want nil if the attribute is nil. So we use has_key? rather than [] + ||.
60
+ if error.has_key?(method.to_s)
61
+ error[method.to_s]
62
+ else
63
+ super
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ #:startdoc:
@@ -0,0 +1,133 @@
1
+ module AWS
2
+ module S3
3
+
4
+ # Abstract super class of all AWS::S3 exceptions
5
+ class S3Exception < StandardError
6
+ end
7
+
8
+ # All responses with a code between 300 and 599 that contain an <Error></Error> body are wrapped in an
9
+ # ErrorResponse which contains an Error object. This Error class generates a custom exception with the name
10
+ # of the xml Error and its message. All such runtime generated exception classes descend from ResponseError
11
+ # and contain the ErrorResponse object so that all code that makes a request can rescue ResponseError and get
12
+ # access to the ErrorResponse.
13
+ class ResponseError < S3Exception
14
+ attr_reader :response
15
+ def initialize(message, response)
16
+ @response = response
17
+ super(message)
18
+ end
19
+ end
20
+
21
+ #:stopdoc:
22
+
23
+ # Most ResponseError's are created just time on a need to have basis, but we explicitly define the
24
+ # InternalError exception because we want to explicitly rescue InternalError in some cases.
25
+ class InternalError < ResponseError
26
+ end
27
+
28
+ class NoSuchKey < ResponseError
29
+ end
30
+
31
+ class RequestTimeout < ResponseError
32
+ end
33
+
34
+ # Abstract super class for all invalid options.
35
+ class InvalidOption < S3Exception
36
+ end
37
+
38
+ # Raised if an invalid value is passed to the <tt>:access</tt> option when creating a Bucket or an S3Object.
39
+ class InvalidAccessControlLevel < InvalidOption
40
+ def initialize(valid_levels, access_level)
41
+ super("Valid access control levels are #{valid_levels.inspect}. You specified `#{access_level}'.")
42
+ end
43
+ end
44
+
45
+ # Raised if either the access key id or secret access key arguments are missing when establishing a connection.
46
+ class MissingAccessKey < InvalidOption
47
+ def initialize(missing_keys)
48
+ key_list = missing_keys.map {|key| key.to_s}.join(' and the ')
49
+ super("You did not provide both required access keys. Please provide the #{key_list}.")
50
+ end
51
+ end
52
+
53
+ # Raised if a request is attempted before any connections have been established.
54
+ class NoConnectionEstablished < S3Exception
55
+ end
56
+
57
+ # Raised if an unrecognized option is passed when establishing a connection.
58
+ class InvalidConnectionOption < InvalidOption
59
+ def initialize(invalid_options)
60
+ message = "The following connection options are invalid: #{invalid_options.join(', ')}. " +
61
+ "The valid connection options are: #{Connection::Options.valid_options.join(', ')}."
62
+ super(message)
63
+ end
64
+ end
65
+
66
+ # Raised if an invalid bucket name is passed when creating a new Bucket.
67
+ class InvalidBucketName < S3Exception
68
+ def initialize(invalid_name)
69
+ message = "`#{invalid_name}' is not a valid bucket name. " +
70
+ "Bucket names must be between 3 and 255 bytes and " +
71
+ "can contain letters, numbers, dashes and underscores."
72
+ super(message)
73
+ end
74
+ end
75
+
76
+ # Raised if an invalid key name is passed when creating an S3Object.
77
+ class InvalidKeyName < S3Exception
78
+ def initialize(invalid_name)
79
+ message = "`#{invalid_name}' is not a valid key name. " +
80
+ "Key names must be no more than 1024 bytes long."
81
+ super(message)
82
+ end
83
+ end
84
+
85
+ # Raised if an invalid value is assigned to an S3Object's specific metadata name.
86
+ class InvalidMetadataValue < S3Exception
87
+ def initialize(invalid_names)
88
+ message = "The following metadata names have invalid values: #{invalid_names.join(', ')}. " +
89
+ "Metadata can not be larger than 2kilobytes."
90
+ super(message)
91
+ end
92
+ end
93
+
94
+ # Raised if the current bucket can not be inferred when not explicitly specifying the target bucket in the calling
95
+ # method's arguments.
96
+ class CurrentBucketNotSpecified < S3Exception
97
+ def initialize(address)
98
+ message = "No bucket name can be inferred from your current connection's address (`#{address}')"
99
+ super(message)
100
+ end
101
+ end
102
+
103
+ # Raised when an orphaned S3Object belonging to no bucket tries to access its (non-existant) bucket.
104
+ class NoBucketSpecified < S3Exception
105
+ def initialize
106
+ super('The current object must have its bucket set')
107
+ end
108
+ end
109
+
110
+ # Raised if an attempt is made to save an S3Object that does not have a key set.
111
+ class NoKeySpecified < S3Exception
112
+ def initialize
113
+ super('The current object must have its key set')
114
+ end
115
+ end
116
+
117
+ # Raised if you try to save a deleted object.
118
+ class DeletedObject < S3Exception
119
+ def initialize
120
+ super('You can not save a deleted object')
121
+ end
122
+ end
123
+
124
+ class ExceptionClassClash < S3Exception #:nodoc:
125
+ def initialize(klass)
126
+ message = "The exception class you tried to create (`#{klass}') exists and is not an exception"
127
+ super(message)
128
+ end
129
+ end
130
+
131
+ #:startdoc:
132
+ end
133
+ end
@@ -0,0 +1,323 @@
1
+ #:stopdoc:
2
+
3
+ class Hash
4
+ def to_query_string(include_question_mark = true)
5
+ query_string = ''
6
+ unless empty?
7
+ query_string << '?' if include_question_mark
8
+ query_string << inject([]) do |params, (key, value)|
9
+ params << "#{key}=#{value}"
10
+ end.join('&')
11
+ end
12
+ query_string
13
+ end
14
+
15
+ def to_normalized_options
16
+ # Convert all option names to downcased strings, and replace underscores with hyphens
17
+ inject({}) do |normalized_options, (name, value)|
18
+ normalized_options[name.to_header] = value.to_s
19
+ normalized_options
20
+ end
21
+ end
22
+
23
+ def to_normalized_options!
24
+ replace(to_normalized_options)
25
+ end
26
+ end
27
+
28
+ class String
29
+ def previous!
30
+ self[-1] -= 1
31
+ self
32
+ end
33
+
34
+ def previous
35
+ dup.previous!
36
+ end
37
+
38
+ def to_header
39
+ downcase.tr('_', '-')
40
+ end
41
+
42
+ # ActiveSupport adds an underscore method to String so let's just use that one if
43
+ # we find that the method is already defined
44
+ def underscore
45
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
46
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
47
+ downcase
48
+ end unless public_method_defined? :underscore
49
+
50
+ def utf8?
51
+ scan(/[^\x00-\xa0]/u) { |s| s.unpack('U') }
52
+ true
53
+ rescue ArgumentError
54
+ false
55
+ end
56
+
57
+ # All paths in in S3 have to be valid unicode so this takes care of
58
+ # cleaning up any strings that aren't valid utf-8 according to String#utf8?
59
+ def remove_extended!
60
+ gsub!(/[\x80-\xFF]/) { "%02X" % $&[0] }
61
+ end
62
+
63
+ def remove_extended
64
+ dup.remove_extended!
65
+ end
66
+ end
67
+
68
+ class CoercibleString < String
69
+ class << self
70
+ def coerce(string)
71
+ new(string).coerce
72
+ end
73
+ end
74
+
75
+ def coerce
76
+ case self
77
+ when 'true': true
78
+ when 'false': false
79
+ # Don't coerce numbers that start with zero
80
+ when /^[1-9]+\d*$/: Integer(self)
81
+ when datetime_format: Time.parse(self)
82
+ else
83
+ self
84
+ end
85
+ end
86
+
87
+ private
88
+ # Lame hack since Date._parse is so accepting. S3 dates are of the form: '2006-10-29T23:14:47.000Z'
89
+ # so unless the string looks like that, don't even try, otherwise it might convert an object's
90
+ # key from something like '03 1-2-3-Apple-Tree.mp3' to Sat Feb 03 00:00:00 CST 2001.
91
+ def datetime_format
92
+ /^\d{4}-\d{2}-\d{2}\w\d{2}:\d{2}:\d{2}/
93
+ end
94
+ end
95
+
96
+ class Symbol
97
+ def to_header
98
+ to_s.to_header
99
+ end
100
+ end
101
+
102
+ module Kernel
103
+ def __method__(depth = 0)
104
+ caller[depth][/`([^']+)'/, 1]
105
+ end if RUBY_VERSION < '1.9'
106
+
107
+ def memoize(reload = false, storage = nil)
108
+ storage = "@#{storage || __method__(1)}"
109
+ if reload
110
+ instance_variable_set(storage, nil)
111
+ else
112
+ if cache = instance_variable_get(storage)
113
+ return cache
114
+ end
115
+ end
116
+ instance_variable_set(storage, yield)
117
+ end
118
+
119
+ def require_library_or_gem(library)
120
+ require library
121
+ rescue LoadError => library_not_installed
122
+ begin
123
+ require 'rubygems'
124
+ require library
125
+ rescue LoadError
126
+ raise library_not_installed
127
+ end
128
+ end
129
+ end
130
+
131
+ class Object
132
+ def returning(value)
133
+ yield(value)
134
+ value
135
+ end
136
+ end
137
+
138
+ class Module
139
+ def memoized(method_name)
140
+ original_method = "unmemoized_#{method_name}_#{Time.now.to_i}"
141
+ alias_method original_method, method_name
142
+ module_eval(<<-EVAL, __FILE__, __LINE__)
143
+ def #{method_name}(reload = false, *args, &block)
144
+ memoize(reload) do
145
+ send(:#{original_method}, *args, &block)
146
+ end
147
+ end
148
+ EVAL
149
+ end
150
+
151
+ def constant(name, value)
152
+ unless const_defined?(name)
153
+ const_set(name, value)
154
+ module_eval(<<-EVAL, __FILE__, __LINE__)
155
+ def self.#{name.to_s.downcase}
156
+ #{name.to_s}
157
+ end
158
+ EVAL
159
+ end
160
+ end
161
+
162
+ # Transforms MarcelBucket into
163
+ #
164
+ # class MarcelBucket < AWS::S3::Bucket
165
+ # set_current_bucket_to 'marcel'
166
+ # end
167
+ def const_missing_from_s3_library(sym)
168
+ if sym.to_s =~ /^(\w+)(Bucket|S3Object)$/
169
+ const = const_set(sym, Class.new(AWS::S3.const_get($2)))
170
+ const.current_bucket = $1.underscore
171
+ const
172
+ else
173
+ const_missing_not_from_s3_library(sym)
174
+ end
175
+ end
176
+ alias_method :const_missing_not_from_s3_library, :const_missing
177
+ alias_method :const_missing, :const_missing_from_s3_library
178
+ end
179
+
180
+
181
+ class Class # :nodoc:
182
+ def cattr_reader(*syms)
183
+ syms.flatten.each do |sym|
184
+ class_eval(<<-EOS, __FILE__, __LINE__)
185
+ unless defined? @@#{sym}
186
+ @@#{sym} = nil
187
+ end
188
+
189
+ def self.#{sym}
190
+ @@#{sym}
191
+ end
192
+
193
+ def #{sym}
194
+ @@#{sym}
195
+ end
196
+ EOS
197
+ end
198
+ end
199
+
200
+ def cattr_writer(*syms)
201
+ syms.flatten.each do |sym|
202
+ class_eval(<<-EOS, __FILE__, __LINE__)
203
+ unless defined? @@#{sym}
204
+ @@#{sym} = nil
205
+ end
206
+
207
+ def self.#{sym}=(obj)
208
+ @@#{sym} = obj
209
+ end
210
+
211
+ def #{sym}=(obj)
212
+ @@#{sym} = obj
213
+ end
214
+ EOS
215
+ end
216
+ end
217
+
218
+ def cattr_accessor(*syms)
219
+ cattr_reader(*syms)
220
+ cattr_writer(*syms)
221
+ end
222
+ end if Class.instance_methods(false).grep(/^cattr_(?:reader|writer|accessor)$/).empty?
223
+
224
+ module SelectiveAttributeProxy
225
+ def self.included(klass)
226
+ klass.extend(ClassMethods)
227
+ klass.class_eval(<<-EVAL, __FILE__, __LINE__)
228
+ cattr_accessor :attribute_proxy
229
+ cattr_accessor :attribute_proxy_options
230
+
231
+ # Default name for attribute storage
232
+ self.attribute_proxy = :attributes
233
+ self.attribute_proxy_options = {:exclusively => true}
234
+
235
+ private
236
+ # By default proxy all attributes
237
+ def proxiable_attribute?(name)
238
+ return true unless self.class.attribute_proxy_options[:exclusively]
239
+ send(self.class.attribute_proxy).has_key?(name)
240
+ end
241
+
242
+ def method_missing(method, *args, &block)
243
+ # Autovivify attribute storage
244
+ if method == self.class.attribute_proxy
245
+ ivar = "@\#{method}"
246
+ instance_variable_set(ivar, {}) unless instance_variable_get(ivar).is_a?(Hash)
247
+ instance_variable_get(ivar)
248
+ # Delegate to attribute storage
249
+ elsif method.to_s =~ /^(\\w+)(=?)$/ && proxiable_attribute?($1)
250
+ attributes_hash_name = self.class.attribute_proxy
251
+ $2.empty? ? send(attributes_hash_name)[$1] : send(attributes_hash_name)[$1] = args.first
252
+ else
253
+ super
254
+ end
255
+ end
256
+ EVAL
257
+ end
258
+
259
+ module ClassMethods
260
+ def proxy_to(attribute_name, options = {})
261
+ if attribute_name.is_a?(Hash)
262
+ options = attribute_name
263
+ else
264
+ self.attribute_proxy = attribute_name
265
+ end
266
+ self.attribute_proxy_options = options
267
+ end
268
+ end
269
+ end
270
+
271
+ # When streaming data up, Net::HTTPGenericRequest hard codes a chunk size of 1k. For large files this
272
+ # is an unfortunately low chunk size, so here we make it use a much larger default size and move it into a method
273
+ # so that the implementation of send_request_with_body_stream doesn't need to be changed to change the chunk size (at least not anymore
274
+ # than I've already had to...).
275
+ module Net
276
+ class HTTPGenericRequest
277
+ def send_request_with_body_stream(sock, ver, path, f)
278
+ raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" unless content_length() or chunked?
279
+ unless content_type()
280
+ warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
281
+ set_content_type 'application/x-www-form-urlencoded'
282
+ end
283
+ write_header sock, ver, path
284
+ if chunked?
285
+ while s = f.read(chunk_size)
286
+ sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
287
+ end
288
+ sock.write "0\r\n\r\n"
289
+ else
290
+ while s = f.read(chunk_size)
291
+ sock.write s
292
+ end
293
+ end
294
+ end
295
+
296
+ def chunk_size
297
+ 1048576 # 1 megabyte
298
+ end
299
+ end
300
+
301
+ # Net::HTTP before 1.8.4 doesn't have the use_ssl? method or the Delete request type
302
+ class HTTP
303
+ def use_ssl?
304
+ @use_ssl
305
+ end unless public_method_defined? :use_ssl?
306
+
307
+ class Delete < HTTPRequest
308
+ METHOD = 'DELETE'
309
+ REQUEST_HAS_BODY = false
310
+ RESPONSE_HAS_BODY = true
311
+ end unless const_defined? :Delete
312
+ end
313
+ end
314
+
315
+ class XmlGenerator < String #:nodoc:
316
+ attr_reader :xml
317
+ def initialize
318
+ @xml = Builder::XmlMarkup.new(:indent => 2, :target => self)
319
+ super()
320
+ build
321
+ end
322
+ end
323
+ #:startdoc: