croaker-aws-s3 0.5.2.20090127001

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/COPYING +19 -0
  2. data/INSTALL +55 -0
  3. data/README +545 -0
  4. data/Rakefile +334 -0
  5. data/bin/s3sh +6 -0
  6. data/bin/setup.rb +10 -0
  7. data/lib/aws/s3/acl.rb +636 -0
  8. data/lib/aws/s3/authentication.rb +222 -0
  9. data/lib/aws/s3/base.rb +257 -0
  10. data/lib/aws/s3/bittorrent.rb +58 -0
  11. data/lib/aws/s3/bucket.rb +329 -0
  12. data/lib/aws/s3/connection.rb +349 -0
  13. data/lib/aws/s3/error.rb +69 -0
  14. data/lib/aws/s3/exceptions.rb +133 -0
  15. data/lib/aws/s3/extensions.rb +324 -0
  16. data/lib/aws/s3/logging.rb +311 -0
  17. data/lib/aws/s3/object.rb +613 -0
  18. data/lib/aws/s3/owner.rb +44 -0
  19. data/lib/aws/s3/parsing.rb +99 -0
  20. data/lib/aws/s3/response.rb +180 -0
  21. data/lib/aws/s3/service.rb +51 -0
  22. data/lib/aws/s3/version.rb +12 -0
  23. data/lib/aws/s3.rb +61 -0
  24. data/support/faster-xml-simple/lib/faster_xml_simple.rb +187 -0
  25. data/support/faster-xml-simple/test/regression_test.rb +47 -0
  26. data/support/faster-xml-simple/test/test_helper.rb +17 -0
  27. data/support/faster-xml-simple/test/xml_simple_comparison_test.rb +46 -0
  28. data/support/rdoc/code_info.rb +211 -0
  29. data/test/acl_test.rb +254 -0
  30. data/test/authentication_test.rb +114 -0
  31. data/test/base_test.rb +136 -0
  32. data/test/bucket_test.rb +74 -0
  33. data/test/connection_test.rb +217 -0
  34. data/test/error_test.rb +70 -0
  35. data/test/extensions_test.rb +331 -0
  36. data/test/fixtures/buckets.yml +133 -0
  37. data/test/fixtures/errors.yml +34 -0
  38. data/test/fixtures/headers.yml +3 -0
  39. data/test/fixtures/logging.yml +15 -0
  40. data/test/fixtures/loglines.yml +5 -0
  41. data/test/fixtures/logs.yml +7 -0
  42. data/test/fixtures/policies.yml +16 -0
  43. data/test/fixtures.rb +89 -0
  44. data/test/logging_test.rb +89 -0
  45. data/test/mocks/fake_response.rb +26 -0
  46. data/test/object_test.rb +205 -0
  47. data/test/parsing_test.rb +66 -0
  48. data/test/remote/acl_test.rb +117 -0
  49. data/test/remote/bittorrent_test.rb +45 -0
  50. data/test/remote/bucket_test.rb +146 -0
  51. data/test/remote/logging_test.rb +82 -0
  52. data/test/remote/object_test.rb +371 -0
  53. data/test/remote/test_helper.rb +30 -0
  54. data/test/response_test.rb +68 -0
  55. data/test/service_test.rb +23 -0
  56. data/test/test_helper.rb +111 -0
  57. metadata +143 -0
@@ -0,0 +1,329 @@
1
+ module AWS
2
+ module S3
3
+ # Buckets are containers for objects (the files you store on S3). To create a new bucket you just specify its name.
4
+ #
5
+ # # Pick a unique name, or else you'll get an error
6
+ # # if the name is already taken.
7
+ # Bucket.create('jukebox')
8
+ #
9
+ # Bucket names must be unique across the entire S3 system, sort of like domain names across the internet. If you try
10
+ # to create a bucket with a name that is already taken, you will get an error.
11
+ #
12
+ # Assuming the name you chose isn't already taken, your new bucket will now appear in the bucket list:
13
+ #
14
+ # Service.buckets
15
+ # # => [#<AWS::S3::Bucket @attributes={"name"=>"jukebox"}>]
16
+ #
17
+ # Once you have succesfully created a bucket you can you can fetch it by name using Bucket.find.
18
+ #
19
+ # music_bucket = Bucket.find('jukebox')
20
+ #
21
+ # The bucket that is returned will contain a listing of all the objects in the bucket.
22
+ #
23
+ # music_bucket.objects.size
24
+ # # => 0
25
+ #
26
+ # If all you are interested in is the objects of the bucket, you can get to them directly using Bucket.objects.
27
+ #
28
+ # Bucket.objects('jukebox').size
29
+ # # => 0
30
+ #
31
+ # By default all objects will be returned, though there are several options you can use to limit what is returned, such as
32
+ # specifying that only objects whose name is after a certain place in the alphabet be returned, and etc. Details about these options can
33
+ # be found in the documentation for Bucket.find.
34
+ #
35
+ # To add an object to a bucket you specify the name of the object, its value, and the bucket to put it in.
36
+ #
37
+ # file = 'black-flowers.mp3'
38
+ # S3Object.store(file, open(file), 'jukebox')
39
+ #
40
+ # You'll see your file has been added to it:
41
+ #
42
+ # music_bucket.objects
43
+ # # => [#<AWS::S3::S3Object '/jukebox/black-flowers.mp3'>]
44
+ #
45
+ # You can treat your bucket like a hash and access objects by name:
46
+ #
47
+ # jukebox['black-flowers.mp3']
48
+ # # => #<AWS::S3::S3Object '/jukebox/black-flowers.mp3'>
49
+ #
50
+ # In the event that you want to delete a bucket, you can use Bucket.delete.
51
+ #
52
+ # Bucket.delete('jukebox')
53
+ #
54
+ # Keep in mind, like unix directories, you can not delete a bucket unless it is empty. Trying to delete a bucket
55
+ # that contains objects will raise a BucketNotEmpty exception.
56
+ #
57
+ # Passing the :force => true option to delete will take care of deleting all the bucket's objects for you.
58
+ #
59
+ # Bucket.delete('photos', :force => true)
60
+ # # => true
61
+ class Bucket < Base
62
+ class << self
63
+ # Creates a bucket named <tt>name</tt>.
64
+ #
65
+ # Bucket.create('jukebox')
66
+ #
67
+ # Your bucket name must be unique across all of S3. If the name
68
+ # you request has already been taken, you will get a 409 Conflict response, and a BucketAlreadyExists exception
69
+ # will be raised.
70
+ #
71
+ # To create a bucket in a different location use option[:location]. The only available location is "eu" at the moment. If the
72
+ # option is ommitted the default location will be used.
73
+ #
74
+ # By default new buckets have their access level set to private. You can override this using the <tt>:access</tt> option.
75
+ #
76
+ # Bucket.create('internet_drop_box', :access => :public_read_write)
77
+ #
78
+ # The full list of access levels that you can set on Bucket and S3Object creation are listed in the README[link:files/README.html]
79
+ # in the section called 'Setting access levels'.
80
+ def create(name, options = {})
81
+ validate_name!(name)
82
+
83
+ # At the moment the only possible option is to create a bucket in the eu. if nothing is specified the default location will be used
84
+ if options[:location] && options[:location] == "eu"
85
+ body = "<CreateBucketConfiguration>
86
+ <LocationConstraint>EU</LocationConstraint>
87
+ </CreateBucketConfiguration>"
88
+ end
89
+ put("/#{name}", options, body).success?
90
+ end
91
+
92
+ # Fetches the bucket named <tt>name</tt>.
93
+ #
94
+ # Bucket.find('jukebox')
95
+ #
96
+ # If a default bucket is inferable from the current connection's subdomain, or if set explicitly with Base.set_current_bucket,
97
+ # it will be used if no bucket is specified.
98
+ #
99
+ # MusicBucket.current_bucket
100
+ # => 'jukebox'
101
+ # MusicBucket.find.name
102
+ # => 'jukebox'
103
+ #
104
+ # By default all objects contained in the bucket will be returned (sans their data) along with the bucket.
105
+ # You can access your objects using the Bucket#objects method.
106
+ #
107
+ # Bucket.find('jukebox').objects
108
+ #
109
+ # There are several options which allow you to limit which objects are retrieved. The list of object filtering options
110
+ # are listed in the documentation for Bucket.objects.
111
+ def find(name = nil, options = {})
112
+ new(get(path(name, options)).bucket)
113
+ end
114
+
115
+ # Return just the objects in the bucket named <tt>name</tt>.
116
+ #
117
+ # By default all objects of the named bucket will be returned. There are options, though, for filtering
118
+ # which objects are returned.
119
+ #
120
+ # === Object filtering options
121
+ #
122
+ # * <tt>:max_keys</tt> - The maximum number of keys you'd like to see in the response body.
123
+ # The server may return fewer than this many keys, but will not return more.
124
+ #
125
+ # Bucket.objects('jukebox').size
126
+ # # => 3
127
+ # Bucket.objects('jukebox', :max_keys => 1).size
128
+ # # => 1
129
+ #
130
+ # * <tt>:prefix</tt> - Restricts the response to only contain results that begin with the specified prefix.
131
+ #
132
+ # Bucket.objects('jukebox')
133
+ # # => [<AWS::S3::S3Object '/jazz/miles.mp3'>, <AWS::S3::S3Object '/jazz/dolphy.mp3'>, <AWS::S3::S3Object '/classical/malher.mp3'>]
134
+ # Bucket.objects('jukebox', :prefix => 'classical')
135
+ # # => [<AWS::S3::S3Object '/classical/malher.mp3'>]
136
+ #
137
+ # * <tt>:marker</tt> - Marker specifies where in the result set to resume listing. It restricts the response
138
+ # to only contain results that occur alphabetically _after_ the value of marker. To retrieve the next set of results,
139
+ # use the last key from the current page of results as the marker in your next request.
140
+ #
141
+ # # Skip 'mahler'
142
+ # Bucket.objects('jukebox', :marker => 'mb')
143
+ # # => [<AWS::S3::S3Object '/jazz/miles.mp3'>]
144
+ #
145
+ # === Examples
146
+ #
147
+ # # Return no more than 2 objects whose key's are listed alphabetically after the letter 'm'.
148
+ # Bucket.objects('jukebox', :marker => 'm', :max_keys => 2)
149
+ # # => [<AWS::S3::S3Object '/jazz/miles.mp3'>, <AWS::S3::S3Object '/classical/malher.mp3'>]
150
+ #
151
+ # # Return no more than 2 objects whose key's are listed alphabetically after the letter 'm' and have the 'jazz' prefix.
152
+ # Bucket.objects('jukebox', :marker => 'm', :max_keys => 2, :prefix => 'jazz')
153
+ # # => [<AWS::S3::S3Object '/jazz/miles.mp3'>]
154
+ def objects(name = nil, options = {})
155
+ find(name, options).object_cache
156
+ end
157
+
158
+ # Deletes the bucket named <tt>name</tt>.
159
+ #
160
+ # All objects in the bucket must be deleted before the bucket can be deleted. If the bucket is not empty,
161
+ # BucketNotEmpty will be raised.
162
+ #
163
+ # You can side step this issue by passing the :force => true option to delete which will take care of
164
+ # emptying the bucket before deleting it.
165
+ #
166
+ # Bucket.delete('photos', :force => true)
167
+ #
168
+ # Only the owner of a bucket can delete a bucket, regardless of the bucket's access control policy.
169
+ def delete(name = nil, options = {})
170
+ find(name).delete_all if options[:force]
171
+
172
+ name = path(name)
173
+ Base.delete(name).success?
174
+ end
175
+
176
+ # List all your buckets. This is a convenient wrapper around AWS::S3::Service.buckets.
177
+ def list(reload = false)
178
+ Service.buckets(reload)
179
+ end
180
+
181
+ private
182
+ def validate_name!(name)
183
+ raise InvalidBucketName.new(name) unless name =~ /^[-\w.]{3,255}$/
184
+ end
185
+
186
+ def path(name, options = {})
187
+ if name.is_a?(Hash)
188
+ options = name
189
+ name = nil
190
+ end
191
+ "/#{bucket_name(name)}#{RequestOptions.process(options).to_query_string}"
192
+ end
193
+ end
194
+
195
+ attr_reader :object_cache #:nodoc:
196
+
197
+ include Enumerable
198
+
199
+ def initialize(attributes = {}) #:nodoc:
200
+ super
201
+ @object_cache = []
202
+ build_contents!
203
+ end
204
+
205
+ # Fetches the object named <tt>object_key</tt>, or nil if the bucket does not contain an object with the
206
+ # specified key.
207
+ #
208
+ # bucket.objects
209
+ # => [#<AWS::S3::S3Object '/marcel_molina/beluga_baby.jpg'>,
210
+ # #<AWS::S3::S3Object '/marcel_molina/tongue_overload.jpg'>]
211
+ # bucket['beluga_baby.jpg']
212
+ # => #<AWS::S3::S3Object '/marcel_molina/beluga_baby.jpg'>
213
+ def [](object_key)
214
+ detect {|file| file.key == object_key.to_s}
215
+ end
216
+
217
+ # Initializes a new S3Object belonging to the current bucket.
218
+ #
219
+ # object = bucket.new_object
220
+ # object.value = data
221
+ # object.key = 'classical/mahler.mp3'
222
+ # object.store
223
+ # bucket.objects.include?(object)
224
+ # => true
225
+ def new_object(attributes = {})
226
+ object = S3Object.new(attributes)
227
+ register(object)
228
+ object
229
+ end
230
+
231
+ # List S3Object's of the bucket.
232
+ #
233
+ # Once fetched the objects will be cached. You can reload the objects by passing <tt>:reload</tt>.
234
+ #
235
+ # bucket.objects(:reload)
236
+ #
237
+ # You can also filter the objects using the same options listed in Bucket.objects.
238
+ #
239
+ # bucket.objects(:prefix => 'jazz')
240
+ #
241
+ # Using these filtering options will implictly reload the objects.
242
+ #
243
+ # To reclaim all the objects for the bucket you can pass in :reload again.
244
+ def objects(options = {})
245
+ if options.is_a?(Hash)
246
+ reload = !options.empty?
247
+ else
248
+ reload = options
249
+ options = {}
250
+ end
251
+
252
+ reload!(options) if reload || object_cache.empty?
253
+ object_cache
254
+ end
255
+
256
+ # Iterates over the objects in the bucket.
257
+ #
258
+ # bucket.each do |object|
259
+ # # Do something with the object ...
260
+ # end
261
+ def each(&block)
262
+ # Dup the collection since we might be destructively modifying the object_cache during the iteration.
263
+ objects.dup.each(&block)
264
+ end
265
+
266
+ # Returns true if there are no objects in the bucket.
267
+ def empty?
268
+ objects.empty?
269
+ end
270
+
271
+ # Returns the number of objects in the bucket.
272
+ def size
273
+ objects.size
274
+ end
275
+
276
+ # Deletes the bucket. See its class method counter part Bucket.delete for caveats about bucket deletion and how to ensure
277
+ # a bucket is deleted no matter what.
278
+ def delete(options = {})
279
+ self.class.delete(name, options)
280
+ end
281
+
282
+ # Delete all files in the bucket. Use with caution. Can not be undone.
283
+ def delete_all
284
+ each do |object|
285
+ object.delete
286
+ end
287
+ self
288
+ end
289
+ alias_method :clear, :delete_all
290
+
291
+ # Buckets observe their objects and have this method called when one of their objects
292
+ # is either stored or deleted.
293
+ def update(action, object) #:nodoc:
294
+ case action
295
+ when :stored then add object unless objects.include?(object)
296
+ when :deleted then object_cache.delete(object)
297
+ end
298
+ end
299
+
300
+ private
301
+ def build_contents!
302
+ return unless has_contents?
303
+ attributes.delete('contents').each do |content|
304
+ add new_object(content)
305
+ end
306
+ end
307
+
308
+ def has_contents?
309
+ attributes.has_key?('contents')
310
+ end
311
+
312
+ def add(object)
313
+ register(object)
314
+ object_cache << object
315
+ end
316
+
317
+ def register(object)
318
+ object.bucket = self
319
+ end
320
+
321
+ def reload!(options = {})
322
+ object_cache.clear
323
+ self.class.objects(name, options).each do |object|
324
+ add object
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,349 @@
1
+ module AWS
2
+ module S3
3
+ class Connection #:nodoc:
4
+ class << self
5
+ def connect(options = {})
6
+ new(options)
7
+ end
8
+
9
+ # Added the ability to remove the bucket from the path.
10
+ def prepare_path(path)
11
+ path = path.remove_extended unless path.utf8?
12
+ URI.escape(path)
13
+ end
14
+ end
15
+
16
+ attr_reader :access_key_id, :secret_access_key, :http, :options
17
+
18
+ # Creates a new connection. Connections make the actual requests to S3, though these requests are usually
19
+ # called from subclasses of Base.
20
+ #
21
+ # For details on establishing connections, check the Connection::Management::ClassMethods.
22
+ def initialize(options = {})
23
+ @options = Options.new(options)
24
+ connect
25
+ end
26
+
27
+ def request(verb, path, headers = {}, body = nil, attempts = 0, &block)
28
+ body.rewind if body.respond_to?(:rewind) unless attempts.zero?
29
+
30
+ requester = Proc.new do
31
+ path = self.class.prepare_path(path) if attempts.zero? # Only escape the path once
32
+ request = request_method(verb).new(path, headers)
33
+ ensure_content_type!(request)
34
+ add_user_agent!(request)
35
+
36
+ authenticate!(request, { :bucket_address => self.subdomain })
37
+ if body
38
+ if body.respond_to?(:read)
39
+ request.body_stream = body
40
+ else
41
+ request.body = body
42
+ end
43
+ request.content_length = body.respond_to?(:lstat) ? body.stat.size : body.size
44
+ else
45
+ request.content_length = 0
46
+ end
47
+
48
+ http.request(request, &block)
49
+ end
50
+
51
+ if persistent?
52
+ http.start unless http.started?
53
+ requester.call
54
+ else
55
+ http.start(&requester)
56
+ end
57
+ rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
58
+ @http = create_connection
59
+ attempts == 3 ? raise : (attempts += 1; retry)
60
+ end
61
+
62
+ def url_for(path, options = {})
63
+ authenticate = options.delete(:authenticated)
64
+ # Default to true unless explicitly false
65
+ authenticate = true if authenticate.nil?
66
+ if self.is_using_default_host?
67
+ bucket_name = AWS::S3.remove_or_extract_bucket_from_path(path, true)
68
+ address = "#{bucket_name}.#{DEFAULT_HOST}"
69
+ path = self.class.prepare_path(AWS::S3.remove_or_extract_bucket_from_path(path))
70
+ else
71
+ address = http.address
72
+ path = self.class.prepare_path(path)
73
+ end
74
+ request = request_method(:get).new(path, {})
75
+ query_string = query_string_authentication(request, options.merge({ :bucket_address => bucket_name }))
76
+ returning "#{protocol(options)}#{address}#{port_string}#{path}" do |url|
77
+ url << "?#{query_string}" if authenticate
78
+ end
79
+ end
80
+
81
+ def subdomain
82
+ http.address[/^([^.]+).#{DEFAULT_HOST}$/, 1]
83
+ end
84
+
85
+ def persistent?
86
+ options[:persistent]
87
+ end
88
+
89
+ def protocol(options = {})
90
+ # This always trumps http.use_ssl?
91
+ if options[:use_ssl] == false
92
+ 'http://'
93
+ elsif options[:use_ssl] || http.use_ssl?
94
+ 'https://'
95
+ else
96
+ 'http://'
97
+ end
98
+ end
99
+
100
+ #
101
+ def is_for_bucket?(bucket)
102
+ bucket == subdomain
103
+ end
104
+
105
+ def has_subdomain?
106
+ !subdomain.nil?
107
+ end
108
+
109
+ def is_using_default_host?
110
+ http.address == DEFAULT_HOST || self.has_subdomain?
111
+ end
112
+
113
+ private
114
+
115
+ def extract_keys!
116
+ missing_keys = []
117
+ extract_key = Proc.new {|key| options[key] || (missing_keys.push(key); nil)}
118
+ @access_key_id = extract_key[:access_key_id]
119
+ @secret_access_key = extract_key[:secret_access_key]
120
+
121
+ raise MissingAccessKey.new(missing_keys) unless missing_keys.empty?
122
+ end
123
+
124
+ def create_connection
125
+ http = http_class.new(options[:server], options[:port])
126
+ http.use_ssl = !options[:use_ssl].nil? || options[:port] == 443
127
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
128
+ http
129
+ end
130
+
131
+ def http_class
132
+ if options.connecting_through_proxy?
133
+ Net::HTTP::Proxy(*options.proxy_settings)
134
+ else
135
+ Net::HTTP
136
+ end
137
+ end
138
+
139
+ def connect
140
+ extract_keys!
141
+ @http = create_connection
142
+ end
143
+
144
+ def port_string
145
+ default_port = options[:use_ssl] ? 443 : 80
146
+ http.port == default_port ? '' : ":#{http.port}"
147
+ end
148
+
149
+ def ensure_content_type!(request)
150
+ request['Content-Type'] ||= 'binary/octet-stream'
151
+ end
152
+
153
+ # Just do Header authentication for now
154
+ def authenticate!(request, options={})
155
+ request['Authorization'] = Authentication::Header.new(request, access_key_id, secret_access_key, options)
156
+ end
157
+
158
+ def add_user_agent!(request)
159
+ request['User-Agent'] ||= "AWS::S3/#{Version}"
160
+ end
161
+
162
+ def query_string_authentication(request, options = {})
163
+ Authentication::QueryString.new(request, access_key_id, secret_access_key, options)
164
+ end
165
+
166
+ def request_method(verb)
167
+ Net::HTTP.const_get(verb.to_s.capitalize)
168
+ end
169
+
170
+ def method_missing(method, *args, &block)
171
+ options[method] || super
172
+ end
173
+
174
+ module Management #:nodoc:
175
+ def self.included(base)
176
+ base.cattr_accessor :connections
177
+ base.connections = {}
178
+ base.extend ClassMethods
179
+ end
180
+
181
+ # Manage the creation and destruction of connections for AWS::S3::Base and its subclasses. Connections are
182
+ # created with establish_connection!.
183
+ module ClassMethods
184
+ # Creates a new connection with which to make requests to the S3 servers for the calling class.
185
+ #
186
+ # AWS::S3::Base.establish_connection!(:access_key_id => '...', :secret_access_key => '...')
187
+ #
188
+ # You can set connections for every subclass of AWS::S3::Base. Once the initial connection is made on
189
+ # Base, all subsequent connections will inherit whatever values you don't specify explictly. This allows you to
190
+ # customize details of the connection, such as what server the requests are made to, by just specifying one
191
+ # option.
192
+ #
193
+ # AWS::S3::Bucket.established_connection!(:use_ssl => true)
194
+ #
195
+ # The Bucket connection would inherit the <tt>:access_key_id</tt> and the <tt>:secret_access_key</tt> from
196
+ # Base's connection. Unlike the Base connection, all Bucket requests would be made over SSL.
197
+ #
198
+ # == Required arguments
199
+ #
200
+ # * <tt>:access_key_id</tt> - The access key id for your S3 account. Provided by Amazon.
201
+ # * <tt>:secret_access_key</tt> - The secret access key for your S3 account. Provided by Amazon.
202
+ #
203
+ # If any of these required arguments is missing, a MissingAccessKey exception will be raised.
204
+ #
205
+ # == Optional arguments
206
+ #
207
+ # * <tt>:server</tt> - The server to make requests to. You can use this to specify your bucket in the subdomain,
208
+ # or your own domain's cname if you are using virtual hosted buckets. Defaults to <tt>s3.amazonaws.com</tt>.
209
+ # * <tt>:port</tt> - The port to the requests should be made on. Defaults to 80 or 443 if the <tt>:use_ssl</tt>
210
+ # argument is set.
211
+ # * <tt>:use_ssl</tt> - Whether requests should be made over SSL. If set to true, the <tt>:port</tt> argument
212
+ # will be implicitly set to 443, unless specified otherwise. Defaults to false.
213
+ # * <tt>:persistent</tt> - Whether to use a persistent connection to the server. Having this on provides around a two fold
214
+ # performance increase but for long running processes some firewalls may find the long lived connection suspicious and close the connection.
215
+ # If you run into connection errors, try setting <tt>:persistent</tt> to false. Defaults to false.
216
+ # * <tt>:proxy</tt> - If you need to connect through a proxy, you can specify your proxy settings by specifying a <tt>:host</tt>, <tt>:port</tt>, <tt>:user</tt>, and <tt>:password</tt>
217
+ # with the <tt>:proxy</tt> option.
218
+ # The <tt>:host</tt> setting is required if specifying a <tt>:proxy</tt>.
219
+ #
220
+ # AWS::S3::Bucket.established_connection!(:proxy => {
221
+ # :host => '...', :port => 8080, :user => 'marcel', :password => 'secret'
222
+ # })
223
+ def establish_connection!(options = {})
224
+ # After you've already established the default connection, just specify
225
+ # the difference for subsequent connections
226
+ options = default_connection.options.merge(options) if connected?
227
+ connections[connection_name] = Connection.connect(options)
228
+ end
229
+
230
+ # Returns the connection for the current class, or Base's default connection if the current class does not
231
+ # have its own connection.
232
+ #
233
+ # If not connection has been established yet, NoConnectionEstablished will be raised.
234
+ def connection
235
+ if connected?
236
+ connections[connection_name] || default_connection
237
+ else
238
+ raise NoConnectionEstablished
239
+ end
240
+ end
241
+
242
+ # Returns true if a connection has been made yet.
243
+ def connected?
244
+ !connections.empty?
245
+ end
246
+
247
+ # Removes the connection for the current class. If there is no connection for the current class, the default
248
+ # connection will be removed.
249
+ def disconnect(name = connection_name)
250
+ name = default_connection unless connections.has_key?(name)
251
+ connection = connections[name]
252
+ connection.http.finish if connection.persistent?
253
+ connections.delete(name)
254
+ end
255
+
256
+ # Clears *all* connections, from all classes, with prejudice.
257
+ def disconnect!
258
+ connections.each_key {|connection| disconnect(connection)}
259
+ end
260
+
261
+ private
262
+ def connection_name
263
+ name
264
+ end
265
+
266
+ def default_connection_name
267
+ 'AWS::S3::Base'
268
+ end
269
+
270
+ def default_connection
271
+ connections[default_connection_name]
272
+ end
273
+ end
274
+ end
275
+
276
+ class Options < Hash #:nodoc:
277
+ class << self
278
+ def valid_options
279
+ [:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy]
280
+ end
281
+ end
282
+
283
+ attr_reader :options
284
+ def initialize(options = {})
285
+ super()
286
+ @options = options
287
+ validate!
288
+ extract_proxy_settings!
289
+ extract_persistent!
290
+ extract_server!
291
+ extract_port!
292
+ extract_remainder!
293
+ end
294
+
295
+ def connecting_through_proxy?
296
+ !self[:proxy].nil?
297
+ end
298
+
299
+ def proxy_settings
300
+ proxy_setting_keys.map do |proxy_key|
301
+ self[:proxy][proxy_key]
302
+ end
303
+ end
304
+
305
+ private
306
+
307
+ def proxy_setting_keys
308
+ [:host, :port, :user, :password]
309
+ end
310
+
311
+ def missing_proxy_settings?
312
+ !self[:proxy].keys.include?(:host)
313
+ end
314
+
315
+ def extract_persistent!
316
+ self[:persistent] = options.has_key?(:persitent) ? options[:persitent] : false
317
+ end
318
+
319
+ def extract_proxy_settings!
320
+ self[:proxy] = options.delete(:proxy) if options.include?(:proxy)
321
+ validate_proxy_settings!
322
+ end
323
+
324
+ def extract_server!
325
+ self[:server] = options.delete(:server) || DEFAULT_HOST
326
+ end
327
+
328
+ def extract_port!
329
+ self[:port] = options.delete(:port) || (options[:use_ssl] ? 443 : 80)
330
+ end
331
+
332
+ def extract_remainder!
333
+ update(options)
334
+ end
335
+
336
+ def validate!
337
+ invalid_options = options.keys.select {|key| !self.class.valid_options.include?(key)}
338
+ raise InvalidConnectionOption.new(invalid_options) unless invalid_options.empty?
339
+ end
340
+
341
+ def validate_proxy_settings!
342
+ if connecting_through_proxy? && missing_proxy_settings?
343
+ raise ArgumentError, "Missing proxy settings. Must specify at least :host."
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end
349
+ end