aliyun-oss-ex 0.7.0.1402831795

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.
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