aliyun-oss-rails4 0.7.0.1448446959

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