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