alancse-aws-s3 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: