aliyun-oss-ex 0.7.0.1402831795

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