aliyun-oss-rails4 0.7.0.1448446959

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 +364 -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,70 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Aliyun
3
+ module OSS
4
+ # Anything you do that makes a request to OSS could result in an error. If it does, the Aliyun::OSS library will raise an exception
5
+ # specific to the error. All exception that are raised as a result of a request returning an error response inherit from the
6
+ # ResponseError exception. So should you choose to rescue any such exception, you can simple rescue ResponseError.
7
+ #
8
+ # 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
9
+ # errors listed at http://docs.aliyunwebservices.com/AliyunOSS/2006-03-01/ErrorCodeList.html):
10
+ #
11
+ # begin
12
+ # Bucket.delete('jukebox')
13
+ # rescue ResponseError => error
14
+ # # ...
15
+ # end
16
+ #
17
+ # Once you've captured the exception, you can extract the error message from OSS, as well as the full error response, which includes
18
+ # things like the HTTP response code:
19
+ #
20
+ # error
21
+ # # => #<Aliyun::OSS::BucketNotEmpty The bucket you tried to delete is not empty>
22
+ # error.message
23
+ # # => "The bucket you tried to delete is not empty"
24
+ # error.response.code
25
+ # # => 409
26
+ #
27
+ # You could use this information to redisplay the error in a way you see fit, or just to log the error and continue on.
28
+ class Error
29
+ #:stopdoc:
30
+ attr_accessor :response
31
+ def initialize(error, response = nil)
32
+ @error = error
33
+ @response = response
34
+ @container = Aliyun::OSS
35
+ find_or_create_exception!
36
+ end
37
+
38
+ def raise
39
+ Kernel.raise exception.new(message, response)
40
+ end
41
+
42
+ private
43
+ attr_reader :error, :exception, :container
44
+
45
+ def find_or_create_exception!
46
+ @exception = container.const_defined?(code) ? find_exception : create_exception
47
+ end
48
+
49
+ def find_exception
50
+ exception_class = container.const_get(code)
51
+ Kernel.raise ExceptionClassClash.new(exception_class) unless exception_class.ancestors.include?(ResponseError)
52
+ exception_class
53
+ end
54
+
55
+ def create_exception
56
+ container.const_set(code, Class.new(ResponseError))
57
+ end
58
+
59
+ def method_missing(method, *args, &block)
60
+ # We actually want nil if the attribute is nil. So we use has_key? rather than [] + ||.
61
+ if error.has_key?(method.to_s)
62
+ error[method.to_s]
63
+ else
64
+ super
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ #:startdoc:
@@ -0,0 +1,134 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Aliyun
3
+ module OSS
4
+
5
+ # Abstract super class of all Aliyun::OSS exceptions
6
+ class OSSException < StandardError
7
+ end
8
+
9
+ # All responses with a code between 300 and 599 that contain an <Error></Error> body are wrapped in an
10
+ # ErrorResponse which contains an Error object. This Error class generates a custom exception with the name
11
+ # of the xml Error and its message. All such runtime generated exception classes descend from ResponseError
12
+ # and contain the ErrorResponse object so that all code that makes a request can rescue ResponseError and get
13
+ # access to the ErrorResponse.
14
+ class ResponseError < OSSException
15
+ attr_reader :response
16
+ def initialize(message, response)
17
+ @response = response
18
+ super(message)
19
+ end
20
+ end
21
+
22
+ #:stopdoc:
23
+
24
+ # Most ResponseError's are created just time on a need to have basis, but we explicitly define the
25
+ # InternalError exception because we want to explicitly rescue InternalError in some cases.
26
+ class InternalError < ResponseError
27
+ end
28
+
29
+ class NoSuchKey < ResponseError
30
+ end
31
+
32
+ class RequestTimeout < ResponseError
33
+ end
34
+
35
+ # Abstract super class for all invalid options.
36
+ class InvalidOption < OSSException
37
+ end
38
+
39
+ # Raised if an invalid value is passed to the <tt>:access</tt> option when creating a Bucket or an OSSObject.
40
+ class InvalidAccessControlLevel < InvalidOption
41
+ def initialize(valid_levels, access_level)
42
+ super("Valid access control levels are #{valid_levels.inspect}. You specified `#{access_level}'.")
43
+ end
44
+ end
45
+
46
+ # Raised if either the access key id or secret access key arguments are missing when establishing a connection.
47
+ class MissingAccessKey < InvalidOption
48
+ def initialize(missing_keys)
49
+ key_list = missing_keys.map {|key| key.to_s}.join(' and the ')
50
+ super("You did not provide both required access keys. Please provide the #{key_list}.")
51
+ end
52
+ end
53
+
54
+ # Raised if a request is attempted before any connections have been established.
55
+ class NoConnectionEstablished < OSSException
56
+ end
57
+
58
+ # Raised if an unrecognized option is passed when establishing a connection.
59
+ class InvalidConnectionOption < InvalidOption
60
+ def initialize(invalid_options)
61
+ message = "The following connection options are invalid: #{invalid_options.join(', ')}. " +
62
+ "The valid connection options are: #{Connection::Options::VALID_OPTIONS.join(', ')}."
63
+ super(message)
64
+ end
65
+ end
66
+
67
+ # Raised if an invalid bucket name is passed when creating a new Bucket.
68
+ class InvalidBucketName < OSSException
69
+ def initialize(invalid_name)
70
+ message = "`#{invalid_name}' is not a valid bucket name. " +
71
+ "Bucket names must be between 3 and 255 bytes and " +
72
+ "can contain letters, numbers, dashes and underscores."
73
+ super(message)
74
+ end
75
+ end
76
+
77
+ # Raised if an invalid key name is passed when creating an OSSObject.
78
+ class InvalidKeyName < OSSException
79
+ def initialize(invalid_name)
80
+ message = "`#{invalid_name}' is not a valid key name. " +
81
+ "Key names must be no more than 1024 bytes long."
82
+ super(message)
83
+ end
84
+ end
85
+
86
+ # Raised if an invalid value is assigned to an OSSObject's specific metadata name.
87
+ class InvalidMetadataValue < OSSException
88
+ def initialize(invalid_names)
89
+ message = "The following metadata names have invalid values: #{invalid_names.join(', ')}. " +
90
+ "Metadata can not be larger than 2kilobytes."
91
+ super(message)
92
+ end
93
+ end
94
+
95
+ # Raised if the current bucket can not be inferred when not explicitly specifying the target bucket in the calling
96
+ # method's arguments.
97
+ class CurrentBucketNotSpecified < OSSException
98
+ def initialize(address)
99
+ message = "No bucket name can be inferred from your current connection's address (`#{address}')"
100
+ super(message)
101
+ end
102
+ end
103
+
104
+ # Raised when an orphaned OSSObject belonging to no bucket tries to access its (non-existant) bucket.
105
+ class NoBucketSpecified < OSSException
106
+ def initialize
107
+ super('The current object must have its bucket set')
108
+ end
109
+ end
110
+
111
+ # Raised if an attempt is made to save an OSSObject that does not have a key set.
112
+ class NoKeySpecified < OSSException
113
+ def initialize
114
+ super('The current object must have its key set')
115
+ end
116
+ end
117
+
118
+ # Raised if you try to save a deleted object.
119
+ class DeletedObject < OSSException
120
+ def initialize
121
+ super('You can not save a deleted object')
122
+ end
123
+ end
124
+
125
+ class ExceptionClassClash < OSSException #:nodoc:
126
+ def initialize(klass)
127
+ message = "The exception class you tried to create (`#{klass}') exists and is not an exception"
128
+ super(message)
129
+ end
130
+ end
131
+
132
+ #:startdoc:
133
+ end
134
+ end
@@ -0,0 +1,364 @@
1
+ # -*- encoding : utf-8 -*-
2
+ #:stopdoc:
3
+
4
+ class Hash
5
+ def to_query_string(include_question_mark = true)
6
+ query_string = ''
7
+ unless empty?
8
+ query_string << '?' if include_question_mark
9
+ query_string << inject([]) do |params, (key, value)|
10
+ params << "#{key}=#{value}"
11
+ end.join('&')
12
+ end
13
+ query_string
14
+ end
15
+
16
+ def to_normalized_options
17
+ # Convert all option names to downcased strings, and replace underscores with hyphens
18
+ inject({}) do |normalized_options, (name, value)|
19
+ normalized_options[name.to_header] = value.to_s
20
+ normalized_options
21
+ end
22
+ end
23
+
24
+ def to_normalized_options!
25
+ replace(to_normalized_options)
26
+ end
27
+ end
28
+
29
+ class String
30
+ if RUBY_VERSION <= '1.9'
31
+ def previous!
32
+ self[-1] -= 1
33
+ self
34
+ end
35
+ else
36
+ def previous!
37
+ self[-1] = (self[-1].ord - 1).chr
38
+ self
39
+ end
40
+ end
41
+
42
+ def tap
43
+ yield(self)
44
+ self
45
+ end unless ''.respond_to?(:tap)
46
+
47
+ def previous
48
+ dup.previous!
49
+ end
50
+
51
+ def to_header
52
+ downcase.tr('_', '-')
53
+ end
54
+
55
+ # ActiveSupport adds an underscore method to String so let's just use that one if
56
+ # we find that the method is already defined
57
+ def underscore
58
+ gsub(/::/, '/').
59
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
60
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
61
+ tr("-", "_").downcase
62
+ end unless public_method_defined? :underscore
63
+
64
+ if RUBY_VERSION >= '1.9'
65
+ def valid_utf8?
66
+ dup.force_encoding('UTF-8').valid_encoding?
67
+ end
68
+ else
69
+ def valid_utf8?
70
+ scan(Regexp.new('[^\x00-\xa0]', nil, 'u')) { |s| s.unpack('U') }
71
+ true
72
+ rescue ArgumentError
73
+ false
74
+ end
75
+ end
76
+
77
+ # All paths in in OSS have to be valid unicode so this takes care of
78
+ # cleaning up any strings that aren't valid utf-8 according to String#valid_utf8?
79
+ if RUBY_VERSION >= '1.9'
80
+ def remove_extended!
81
+ sanitized_string = ''
82
+ each_byte do |byte|
83
+ character = byte.chr
84
+ sanitized_string << character if character.ascii_only?
85
+ end
86
+ sanitized_string
87
+ end
88
+ else
89
+ def remove_extended!
90
+ #gsub!(/[\x80-\xFF]/) { "%02X" % $&[0] }
91
+ gsub!(Regexp.new('[\x80-\xFF]')) { "%02X" % $&[0] }
92
+ end
93
+ end
94
+
95
+ def remove_extended
96
+ dup.remove_extended!
97
+ end
98
+ end
99
+
100
+ class CoercibleString < String
101
+ class << self
102
+ def coerce(string)
103
+ new(string).coerce
104
+ end
105
+ end
106
+
107
+ def coerce
108
+ case self
109
+ when 'true'; true
110
+ when 'false'; false
111
+ # Don't coerce numbers that start with zero
112
+ when /^[1-9]+\d*$/; Integer(self)
113
+ when datetime_format; Time.parse(self)
114
+ else
115
+ self
116
+ end
117
+ end
118
+
119
+ private
120
+ # Lame hack since Date._parse is so accepting. OSS dates are of the form: '2006-10-29T23:14:47.000Z'
121
+ # so unless the string looks like that, don't even try, otherwise it might convert an object's
122
+ # key from something like '03 1-2-3-Apple-Tree.mp3' to Sat Feb 03 00:00:00 CST 2001.
123
+ def datetime_format
124
+ /^\d{4}-\d{2}-\d{2}\w\d{2}:\d{2}:\d{2}/
125
+ end
126
+ end
127
+
128
+ class Symbol
129
+ def to_header
130
+ to_s.to_header
131
+ end
132
+ end
133
+
134
+ module Kernel
135
+ def __method__(depth = 0)
136
+ caller[depth][/`([^']+)'/, 1]
137
+ end if RUBY_VERSION <= '1.8.7'
138
+
139
+ def __called_from__
140
+ caller[1][/`([^']+)'/, 1]
141
+ end if RUBY_VERSION > '1.8.7'
142
+
143
+ def expirable_memoize(reload = false, storage = nil)
144
+ current_method = RUBY_VERSION > '1.8.7' ? __called_from__ : __method__(1)
145
+ storage = "@#{storage || current_method}"
146
+ if reload
147
+ instance_variable_set(storage, nil)
148
+ else
149
+ if cache = instance_variable_get(storage)
150
+ return cache
151
+ end
152
+ end
153
+ instance_variable_set(storage, yield)
154
+ end
155
+
156
+ def require_library_or_gem(library, gem_name = nil)
157
+ if RUBY_VERSION >= '1.9'
158
+ gem(gem_name || library, '>=0')
159
+ end
160
+ require library
161
+ rescue LoadError => library_not_installed
162
+ begin
163
+ require 'rubygems'
164
+ require library
165
+ rescue LoadError
166
+ raise library_not_installed
167
+ end
168
+ end
169
+ end
170
+
171
+ class Object
172
+ def returning(value)
173
+ yield(value)
174
+ value
175
+ end
176
+ end
177
+
178
+ class Module
179
+ def memoized(method_name)
180
+ original_method = "unmemoized_#{method_name}_#{Time.now.to_i}"
181
+ alias_method original_method, method_name
182
+ module_eval(<<-EVAL, __FILE__, __LINE__)
183
+ def #{method_name}(reload = false, *args, &block)
184
+ expirable_memoize(reload) do
185
+ send(:#{original_method}, *args, &block)
186
+ end
187
+ end
188
+ EVAL
189
+ end
190
+
191
+ def constant(name, value)
192
+ unless const_defined?(name)
193
+ const_set(name, value)
194
+ module_eval(<<-EVAL, __FILE__, __LINE__)
195
+ def self.#{name.to_s.downcase}
196
+ #{name.to_s}
197
+ end
198
+ EVAL
199
+ end
200
+ end
201
+
202
+ # Transforms MarcelBucket into
203
+ #
204
+ # class MarcelBucket < Aliyun::OSS::Bucket
205
+ # set_current_bucket_to 'marcel'
206
+ # end
207
+ def const_missing_from_oss_library(sym)
208
+ if sym.to_s =~ /^(\w+)(Bucket|OSSObject)$/
209
+ const = const_set(sym, Class.new(Aliyun::OSS.const_get($2)))
210
+ const.current_bucket = $1.underscore
211
+ const
212
+ else
213
+ const_missing_not_from_oss_library(sym)
214
+ end
215
+ end
216
+ alias_method :const_missing_not_from_oss_library, :const_missing
217
+ alias_method :const_missing, :const_missing_from_oss_library
218
+ end
219
+
220
+
221
+ class Class # :nodoc:
222
+ def cattr_reader(*syms)
223
+ syms.flatten.each do |sym|
224
+ class_eval(<<-EOS, __FILE__, __LINE__)
225
+ unless defined? @@#{sym}
226
+ @@#{sym} = nil
227
+ end
228
+
229
+ def self.#{sym}
230
+ @@#{sym}
231
+ end
232
+
233
+ def #{sym}
234
+ @@#{sym}
235
+ end
236
+ EOS
237
+ end
238
+ end
239
+
240
+ def cattr_writer(*syms)
241
+ syms.flatten.each do |sym|
242
+ class_eval(<<-EOS, __FILE__, __LINE__)
243
+ unless defined? @@#{sym}
244
+ @@#{sym} = nil
245
+ end
246
+
247
+ def self.#{sym}=(obj)
248
+ @@#{sym} = obj
249
+ end
250
+
251
+ def #{sym}=(obj)
252
+ @@#{sym} = obj
253
+ end
254
+ EOS
255
+ end
256
+ end
257
+
258
+ def cattr_accessor(*syms)
259
+ cattr_reader(*syms)
260
+ cattr_writer(*syms)
261
+ end
262
+ end if Module.instance_methods(false).grep(/^cattr_(?:reader|writer|accessor)$/).empty?
263
+
264
+ module SelectiveAttributeProxy
265
+ def self.included(klass)
266
+ klass.extend(ClassMethods)
267
+ klass.class_eval(<<-EVAL, __FILE__, __LINE__)
268
+ cattr_accessor :attribute_proxy
269
+ cattr_accessor :attribute_proxy_options
270
+
271
+ # Default name for attribute storage
272
+ self.attribute_proxy = :attributes
273
+ self.attribute_proxy_options = {:exclusively => true}
274
+
275
+ private
276
+ # By default proxy all attributes
277
+ def proxiable_attribute?(name)
278
+ return true unless self.class.attribute_proxy_options[:exclusively]
279
+ send(self.class.attribute_proxy).has_key?(name)
280
+ end
281
+
282
+ def method_missing(method, *args, &block)
283
+ # Autovivify attribute storage
284
+ if method == self.class.attribute_proxy
285
+ ivar = "@\#{method}"
286
+ instance_variable_set(ivar, {}) unless instance_variable_get(ivar).is_a?(Hash)
287
+ instance_variable_get(ivar)
288
+ # Delegate to attribute storage
289
+ elsif method.to_s =~ /^(\\w+)(=?)$/ && proxiable_attribute?($1)
290
+ attributes_hash_name = self.class.attribute_proxy
291
+ $2.empty? ? send(attributes_hash_name)[$1] : send(attributes_hash_name)[$1] = args.first
292
+ else
293
+ super
294
+ end
295
+ end
296
+ EVAL
297
+ end
298
+
299
+ module ClassMethods
300
+ def proxy_to(attribute_name, options = {})
301
+ if attribute_name.is_a?(Hash)
302
+ options = attribute_name
303
+ else
304
+ self.attribute_proxy = attribute_name
305
+ end
306
+ self.attribute_proxy_options = options
307
+ end
308
+ end
309
+ end
310
+
311
+ # When streaming data up, Net::HTTPGenericRequest hard codes a chunk size of 1k. For large files this
312
+ # is an unfortunately low chunk size, so here we make it use a much larger default size and move it into a method
313
+ # 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
314
+ # than I've already had to...).
315
+ module Net
316
+ class HTTPGenericRequest
317
+ def send_request_with_body_stream(sock, ver, path, f)
318
+ raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" unless content_length() or chunked?
319
+ unless content_type()
320
+ warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
321
+ set_content_type 'application/x-www-form-urlencoded'
322
+ end
323
+ write_header sock, ver, path
324
+ if chunked?
325
+ while s = f.read(chunk_size)
326
+ sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
327
+ end
328
+ sock.write "0\r\n\r\n"
329
+ else
330
+ while s = f.read(chunk_size)
331
+ sock.write s
332
+ end
333
+ end
334
+ end
335
+
336
+ def chunk_size
337
+ 1048576 # 1 megabyte
338
+ end
339
+ end
340
+
341
+ # Net::HTTP before 1.8.4 doesn't have the use_ssl? method or the Delete request type
342
+ class HTTP
343
+ def use_ssl?
344
+ @use_ssl
345
+ end unless public_method_defined? :use_ssl?
346
+
347
+ class Delete < HTTPRequest
348
+ METHOD = 'DELETE'
349
+ REQUEST_HAS_BODY = false
350
+ RESPONSE_HAS_BODY = true
351
+ end unless const_defined? :Delete
352
+ end
353
+ end
354
+
355
+ class XmlGenerator < String #:nodoc:
356
+ attr_reader :xml
357
+ def initialize
358
+ @xml = Builder::XmlMarkup.new(:indent => 2, :target => self)
359
+ super()
360
+ build
361
+ end
362
+ end
363
+ #:startdoc:
364
+