ryanking-aws-s3 0.5.1.200811101723

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,324 @@
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(/::/, '/').
46
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
47
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
48
+ tr("-", "_").downcase
49
+ end unless public_method_defined? :underscore
50
+
51
+ def utf8?
52
+ scan(/[^\x00-\xa0]/u) { |s| s.unpack('U') }
53
+ true
54
+ rescue ArgumentError
55
+ false
56
+ end
57
+
58
+ # All paths in in S3 have to be valid unicode so this takes care of
59
+ # cleaning up any strings that aren't valid utf-8 according to String#utf8?
60
+ def remove_extended!
61
+ gsub!(/[\x80-\xFF]/) { "%02X" % $&[0] }
62
+ end
63
+
64
+ def remove_extended
65
+ dup.remove_extended!
66
+ end
67
+ end
68
+
69
+ class CoercibleString < String
70
+ class << self
71
+ def coerce(string)
72
+ new(string).coerce
73
+ end
74
+ end
75
+
76
+ def coerce
77
+ case self
78
+ when 'true': true
79
+ when 'false': false
80
+ # Don't coerce numbers that start with zero
81
+ when /^[1-9]+\d*$/: Integer(self)
82
+ when datetime_format: Time.parse(self)
83
+ else
84
+ self
85
+ end
86
+ end
87
+
88
+ private
89
+ # Lame hack since Date._parse is so accepting. S3 dates are of the form: '2006-10-29T23:14:47.000Z'
90
+ # so unless the string looks like that, don't even try, otherwise it might convert an object's
91
+ # key from something like '03 1-2-3-Apple-Tree.mp3' to Sat Feb 03 00:00:00 CST 2001.
92
+ def datetime_format
93
+ /^\d{4}-\d{2}-\d{2}\w\d{2}:\d{2}:\d{2}/
94
+ end
95
+ end
96
+
97
+ class Symbol
98
+ def to_header
99
+ to_s.to_header
100
+ end
101
+ end
102
+
103
+ module Kernel
104
+ def __method__(depth = 0)
105
+ caller[depth][/`([^']+)'/, 1]
106
+ end #if RUBY_VERSION < '1.8.7'
107
+
108
+ def memoize(reload = false, storage = nil)
109
+ storage = "@#{storage || __method__(1)}"
110
+ if reload
111
+ instance_variable_set(storage, nil)
112
+ else
113
+ if cache = instance_variable_get(storage)
114
+ return cache
115
+ end
116
+ end
117
+ instance_variable_set(storage, yield)
118
+ end
119
+
120
+ def require_library_or_gem(library)
121
+ require library
122
+ rescue LoadError => library_not_installed
123
+ begin
124
+ require 'rubygems'
125
+ require library
126
+ rescue LoadError
127
+ raise library_not_installed
128
+ end
129
+ end
130
+ end
131
+
132
+ class Object
133
+ def returning(value)
134
+ yield(value)
135
+ value
136
+ end
137
+ end
138
+
139
+ class Module
140
+ def memoized(method_name)
141
+ original_method = "unmemoized_#{method_name}_#{Time.now.to_i}"
142
+ alias_method original_method, method_name
143
+ module_eval(<<-EVAL, __FILE__, __LINE__)
144
+ def #{method_name}(reload = false, *args, &block)
145
+ memoize(reload) do
146
+ send(:#{original_method}, *args, &block)
147
+ end
148
+ end
149
+ EVAL
150
+ end
151
+
152
+ def constant(name, value)
153
+ unless const_defined?(name)
154
+ const_set(name, value)
155
+ module_eval(<<-EVAL, __FILE__, __LINE__)
156
+ def self.#{name.to_s.downcase}
157
+ #{name.to_s}
158
+ end
159
+ EVAL
160
+ end
161
+ end
162
+
163
+ # Transforms MarcelBucket into
164
+ #
165
+ # class MarcelBucket < AWS::S3::Bucket
166
+ # set_current_bucket_to 'marcel'
167
+ # end
168
+ def const_missing_from_s3_library(sym)
169
+ if sym.to_s =~ /^(\w+)(Bucket|S3Object)$/
170
+ const = const_set(sym, Class.new(AWS::S3.const_get($2)))
171
+ const.current_bucket = $1.underscore
172
+ const
173
+ else
174
+ const_missing_not_from_s3_library(sym)
175
+ end
176
+ end
177
+ alias_method :const_missing_not_from_s3_library, :const_missing
178
+ alias_method :const_missing, :const_missing_from_s3_library
179
+ end
180
+
181
+
182
+ class Class # :nodoc:
183
+ def cattr_reader(*syms)
184
+ syms.flatten.each do |sym|
185
+ class_eval(<<-EOS, __FILE__, __LINE__)
186
+ unless defined? @@#{sym}
187
+ @@#{sym} = nil
188
+ end
189
+
190
+ def self.#{sym}
191
+ @@#{sym}
192
+ end
193
+
194
+ def #{sym}
195
+ @@#{sym}
196
+ end
197
+ EOS
198
+ end
199
+ end
200
+
201
+ def cattr_writer(*syms)
202
+ syms.flatten.each do |sym|
203
+ class_eval(<<-EOS, __FILE__, __LINE__)
204
+ unless defined? @@#{sym}
205
+ @@#{sym} = nil
206
+ end
207
+
208
+ def self.#{sym}=(obj)
209
+ @@#{sym} = obj
210
+ end
211
+
212
+ def #{sym}=(obj)
213
+ @@#{sym} = obj
214
+ end
215
+ EOS
216
+ end
217
+ end
218
+
219
+ def cattr_accessor(*syms)
220
+ cattr_reader(*syms)
221
+ cattr_writer(*syms)
222
+ end
223
+ end if Class.instance_methods(false).grep(/^cattr_(?:reader|writer|accessor)$/).empty?
224
+
225
+ module SelectiveAttributeProxy
226
+ def self.included(klass)
227
+ klass.extend(ClassMethods)
228
+ klass.class_eval(<<-EVAL, __FILE__, __LINE__)
229
+ cattr_accessor :attribute_proxy
230
+ cattr_accessor :attribute_proxy_options
231
+
232
+ # Default name for attribute storage
233
+ self.attribute_proxy = :attributes
234
+ self.attribute_proxy_options = {:exclusively => true}
235
+
236
+ private
237
+ # By default proxy all attributes
238
+ def proxiable_attribute?(name)
239
+ return true unless self.class.attribute_proxy_options[:exclusively]
240
+ send(self.class.attribute_proxy).has_key?(name)
241
+ end
242
+
243
+ def method_missing(method, *args, &block)
244
+ # Autovivify attribute storage
245
+ if method == self.class.attribute_proxy
246
+ ivar = "@\#{method}"
247
+ instance_variable_set(ivar, {}) unless instance_variable_get(ivar).is_a?(Hash)
248
+ instance_variable_get(ivar)
249
+ # Delegate to attribute storage
250
+ elsif method.to_s =~ /^(\\w+)(=?)$/ && proxiable_attribute?($1)
251
+ attributes_hash_name = self.class.attribute_proxy
252
+ $2.empty? ? send(attributes_hash_name)[$1] : send(attributes_hash_name)[$1] = args.first
253
+ else
254
+ super
255
+ end
256
+ end
257
+ EVAL
258
+ end
259
+
260
+ module ClassMethods
261
+ def proxy_to(attribute_name, options = {})
262
+ if attribute_name.is_a?(Hash)
263
+ options = attribute_name
264
+ else
265
+ self.attribute_proxy = attribute_name
266
+ end
267
+ self.attribute_proxy_options = options
268
+ end
269
+ end
270
+ end
271
+
272
+ # When streaming data up, Net::HTTPGenericRequest hard codes a chunk size of 1k. For large files this
273
+ # is an unfortunately low chunk size, so here we make it use a much larger default size and move it into a method
274
+ # 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
275
+ # than I've already had to...).
276
+ module Net
277
+ class HTTPGenericRequest
278
+ def send_request_with_body_stream(sock, ver, path, f)
279
+ raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" unless content_length() or chunked?
280
+ unless content_type()
281
+ warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
282
+ set_content_type 'application/x-www-form-urlencoded'
283
+ end
284
+ write_header sock, ver, path
285
+ if chunked?
286
+ while s = f.read(chunk_size)
287
+ sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
288
+ end
289
+ sock.write "0\r\n\r\n"
290
+ else
291
+ while s = f.read(chunk_size)
292
+ sock.write s
293
+ end
294
+ end
295
+ end
296
+
297
+ def chunk_size
298
+ 1048576 # 1 megabyte
299
+ end
300
+ end
301
+
302
+ # Net::HTTP before 1.8.4 doesn't have the use_ssl? method or the Delete request type
303
+ class HTTP
304
+ def use_ssl?
305
+ @use_ssl
306
+ end unless public_method_defined? :use_ssl?
307
+
308
+ class Delete < HTTPRequest
309
+ METHOD = 'DELETE'
310
+ REQUEST_HAS_BODY = false
311
+ RESPONSE_HAS_BODY = true
312
+ end unless const_defined? :Delete
313
+ end
314
+ end
315
+
316
+ class XmlGenerator < String #:nodoc:
317
+ attr_reader :xml
318
+ def initialize
319
+ @xml = Builder::XmlMarkup.new(:indent => 2, :target => self)
320
+ super()
321
+ build
322
+ end
323
+ end
324
+ #:startdoc: