scashin133-s3 0.3.8

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.
@@ -0,0 +1,222 @@
1
+ module S3
2
+
3
+ # Class responsible for handling connections to amazon hosts
4
+ class Connection
5
+ include Parser
6
+
7
+ attr_accessor :access_key_id, :secret_access_key, :use_ssl, :timeout, :debug, :proxy
8
+ alias :use_ssl? :use_ssl
9
+
10
+ # Creates new connection object.
11
+ #
12
+ # ==== Options
13
+ # * <tt>:access_key_id</tt> - Access key id (REQUIRED)
14
+ # * <tt>:secret_access_key</tt> - Secret access key (REQUIRED)
15
+ # * <tt>:use_ssl</tt> - Use https or http protocol (false by
16
+ # default)
17
+ # * <tt>:debug</tt> - Display debug information on the STDOUT
18
+ # (false by default)
19
+ # * <tt>:timeout</tt> - Timeout to use by the Net::HTTP object
20
+ # (60 by default)
21
+ # * <tt>:proxy</tt> - Hash for Net::HTTP Proxy settings
22
+ # { :host => "proxy.mydomain.com", :port => "80, :user => "user_a", :password => "secret" }
23
+ # * <tt>:proxy</tt> - Hash for Net::HTTP Proxy settings
24
+ # * <tt>:chunk_size</tt> - Size of a chunk when streaming
25
+ # (1048576 (1 MiB) by default)
26
+ def initialize(options = {})
27
+ @access_key_id = options.fetch(:access_key_id)
28
+ @secret_access_key = options.fetch(:secret_access_key)
29
+ @use_ssl = options.fetch(:use_ssl, false)
30
+ @debug = options.fetch(:debug, false)
31
+ @timeout = options.fetch(:timeout, 60)
32
+ @proxy = options.fetch(:proxy, nil)
33
+ @chunk_size = options.fetch(:chunk_size, 1048576)
34
+ end
35
+
36
+ # Makes request with given HTTP method, sets missing parameters,
37
+ # adds signature to request header and returns response object
38
+ # (Net::HTTPResponse)
39
+ #
40
+ # ==== Parameters
41
+ # * <tt>method</tt> - HTTP Method symbol, can be <tt>:get</tt>,
42
+ # <tt>:put</tt>, <tt>:delete</tt>
43
+ #
44
+ # ==== Options:
45
+ # * <tt>:host</tt> - Hostname to connecto to, defaults
46
+ # to <tt>s3.amazonaws.com</tt>
47
+ # * <tt>:path</tt> - path to send request to (REQUIRED)
48
+ # * <tt>:body</tt> - Request body, only meaningful for
49
+ # <tt>:put</tt> request
50
+ # * <tt>:params</tt> - Parameters to add to query string for
51
+ # request, can be String or Hash
52
+ # * <tt>:headers</tt> - Hash of headers fields to add to request
53
+ # header
54
+ #
55
+ # ==== Returns
56
+ # Net::HTTPResponse object -- response from the server
57
+ def request(method, options)
58
+ host = options.fetch(:host, HOST)
59
+ path = options.fetch(:path)
60
+ body = options.fetch(:body, nil)
61
+ params = options.fetch(:params, {})
62
+ headers = options.fetch(:headers, {})
63
+
64
+ if params
65
+ params = params.is_a?(String) ? params : self.class.parse_params(params)
66
+ path << "?#{params}"
67
+ end
68
+
69
+ path = URI.escape(path)
70
+ request = Request.new(@chunk_size, method.to_s.upcase, !!body, method.to_s.upcase != "HEAD", path)
71
+
72
+ headers = self.class.parse_headers(headers)
73
+ headers.each do |key, value|
74
+ request[key] = value
75
+ end
76
+
77
+ if body
78
+ if body.respond_to?(:read)
79
+ request.body_stream = body
80
+ else
81
+ request.body = body
82
+ end
83
+ request.content_length = body.respond_to?(:lstat) ? body.stat.size : body.size
84
+ end
85
+
86
+ send_request(host, request)
87
+ end
88
+
89
+ # Helper function to parser parameters and create single string of
90
+ # params added to questy string
91
+ #
92
+ # ==== Parameters
93
+ # * <tt>params</tt> - Hash of parameters
94
+ #
95
+ # ==== Returns
96
+ # String -- containing all parameters joined in one params string,
97
+ # i.e. <tt>param1=val&param2&param3=0</tt>
98
+ def self.parse_params(params)
99
+ interesting_keys = [:max_keys, :prefix, :marker, :delimiter, :location]
100
+
101
+ result = []
102
+ params.each do |key, value|
103
+ if interesting_keys.include?(key)
104
+ parsed_key = key.to_s.gsub("_", "-")
105
+ case value
106
+ when nil
107
+ result << parsed_key
108
+ else
109
+ result << "#{parsed_key}=#{value}"
110
+ end
111
+ end
112
+ end
113
+ result.join("&")
114
+ end
115
+
116
+ # Helper function to change headers from symbols, to in correct
117
+ # form (i.e. with '-' instead of '_')
118
+ #
119
+ # ==== Parameters
120
+ # * <tt>headers</tt> - Hash of pairs <tt>headername => value</tt>,
121
+ # where value can be Range (for Range header) or any other value
122
+ # which can be translated to string
123
+ #
124
+ # ==== Returns
125
+ # Hash of headers translated from symbol to string, containing
126
+ # only interesting headers
127
+ def self.parse_headers(headers)
128
+ interesting_keys = [:content_type, :cache_control, :x_amz_acl, :x_amz_storage_class, :range,
129
+ :if_modified_since, :if_unmodified_since,
130
+ :if_match, :if_none_match,
131
+ :content_disposition, :content_encoding,
132
+ :x_amz_copy_source, :x_amz_metadata_directive,
133
+ :x_amz_copy_source_if_match,
134
+ :x_amz_copy_source_if_none_match,
135
+ :x_amz_copy_source_if_unmodified_since,
136
+ :x_amz_copy_source_if_modified_since]
137
+
138
+ parsed_headers = {}
139
+ if headers
140
+ headers.each do |key, value|
141
+ if interesting_keys.include?(key)
142
+ parsed_key = key.to_s.gsub("_", "-")
143
+ parsed_value = value
144
+ case value
145
+ when Range
146
+ parsed_value = "bytes=#{value.first}-#{value.last}"
147
+ end
148
+ parsed_headers[parsed_key] = parsed_value
149
+ end
150
+ end
151
+ end
152
+ parsed_headers
153
+ end
154
+
155
+ private
156
+
157
+ def port
158
+ use_ssl ? 443 : 80
159
+ end
160
+
161
+ def proxy_settings
162
+ @proxy.values_at(:host, :port, :user, :password) unless @proxy.nil? || @proxy.empty?
163
+ end
164
+
165
+ def http(host)
166
+ http = Net::HTTP.new(host, port, *proxy_settings)
167
+ http.set_debug_output(STDOUT) if @debug
168
+ http.use_ssl = @use_ssl
169
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @use_ssl
170
+ http.read_timeout = @timeout if @timeout
171
+ http
172
+ end
173
+
174
+ def send_request(host, request, skip_authorization = false)
175
+ response = http(host).start do |http|
176
+ host = http.address
177
+
178
+ request["Date"] ||= Time.now.httpdate
179
+
180
+ if request.body
181
+ request["Content-Type"] ||= "application/octet-stream"
182
+ request["Content-MD5"] = Base64.encode64(Digest::MD5.digest(request.body)).chomp unless request.body.empty?
183
+ end
184
+
185
+ unless skip_authorization
186
+ request["Authorization"] = Signature.generate(:host => host,
187
+ :request => request,
188
+ :access_key_id => access_key_id,
189
+ :secret_access_key => secret_access_key)
190
+ end
191
+
192
+ http.request(request)
193
+ end
194
+
195
+ if response.code.to_i == 307
196
+ if response.body
197
+ doc = Document.new response.body
198
+ send_request(doc.elements["Error"].elements["Endpoint"].text, request, true)
199
+ end
200
+ else
201
+ handle_response(response)
202
+ end
203
+ end
204
+
205
+ def handle_response(response)
206
+ case response.code.to_i
207
+ when 200...300
208
+ response
209
+ when 300...600
210
+ if response.body.nil? || response.body.empty?
211
+ raise Error::ResponseError.new(nil, response)
212
+ else
213
+ code, message = parse_error(response.body)
214
+ raise Error::ResponseError.exception(code).new(message, response)
215
+ end
216
+ else
217
+ raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
218
+ end
219
+ response
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,110 @@
1
+ module S3
2
+ module Error
3
+
4
+ # All responses with a code between 300 and 599 that contain an
5
+ # <Error></Error> body are wrapped in an ErrorResponse which
6
+ # contains an Error object. This Error class generates a custom
7
+ # exception with the name of the xml Error and its message. All
8
+ # such runtime generated exception classes descend from
9
+ # ResponseError and contain the ErrorResponse object so that all
10
+ # code that makes a request can rescue ResponseError and get
11
+ # access to the ErrorResponse.
12
+ class ResponseError < StandardError
13
+ attr_reader :response
14
+
15
+ # Creates new S3::ResponseError.
16
+ #
17
+ # ==== Parameters
18
+ # * <tt>message</tt> - what went wrong
19
+ # * <tt>response</tt> - Net::HTTPResponse object or nil
20
+ def initialize(message, response)
21
+ @response = response
22
+ super(message)
23
+ end
24
+
25
+ # Factory for all other Exception classes in module, each for
26
+ # every error response available from AmazonAWS
27
+ #
28
+ # ==== Parameters
29
+ # * <tt>code</tt> - Code name of exception
30
+ #
31
+ # ==== Returns
32
+ # Descendant of ResponseError suitable for that exception code
33
+ # or ResponseError class if no class found
34
+ def self.exception(code)
35
+ S3::Error.const_get(code)
36
+ rescue NameError
37
+ ResponseError
38
+ end
39
+ end
40
+
41
+ #:stopdoc:
42
+
43
+ class AccessDenied < ResponseError; end
44
+ class AccountProblem < ResponseError; end
45
+ class AmbiguousGrantByEmailAddress < ResponseError; end
46
+ class BadDigest < ResponseError; end
47
+ class BucketAlreadyExists < ResponseError; end
48
+ class BucketAlreadyOwnedByYou < ResponseError; end
49
+ class BucketNotEmpty < ResponseError; end
50
+ class CredentialsNotSupported < ResponseError; end
51
+ class CrossLocationLoggingProhibited < ResponseError; end
52
+ class EntityTooSmall < ResponseError; end
53
+ class EntityTooLarge < ResponseError; end
54
+ class ExpiredToken < ResponseError; end
55
+ class IncompleteBody < ResponseError; end
56
+ class IncorrectNumberOfFilesInPostRequestPOST < ResponseError; end
57
+ class InlineDataTooLarge < ResponseError; end
58
+ class InternalError < ResponseError; end
59
+ class InvalidAccessKeyId < ResponseError; end
60
+ class InvalidAddressingHeader < ResponseError; end
61
+ class InvalidArgument < ResponseError; end
62
+ class InvalidBucketName < ResponseError; end
63
+ class InvalidDigest < ResponseError; end
64
+ class InvalidLocationConstraint < ResponseError; end
65
+ class InvalidPayer < ResponseError; end
66
+ class InvalidPolicyDocument < ResponseError; end
67
+ class InvalidRange < ResponseError; end
68
+ class InvalidSecurity < ResponseError; end
69
+ class InvalidSOAPRequest < ResponseError; end
70
+ class InvalidStorageClass < ResponseError; end
71
+ class InvalidTargetBucketForLogging < ResponseError; end
72
+ class InvalidToken < ResponseError; end
73
+ class InvalidURI < ResponseError; end
74
+ class KeyTooLong < ResponseError; end
75
+ class MalformedACLError < ResponseError; end
76
+ class MalformedACLError < ResponseError; end
77
+ class MalformedPOSTRequest < ResponseError; end
78
+ class MalformedXML < ResponseError; end
79
+ class MaxMessageLengthExceeded < ResponseError; end
80
+ class MaxPostPreDataLengthExceededErrorYour < ResponseError; end
81
+ class MetadataTooLarge < ResponseError; end
82
+ class MethodNotAllowed < ResponseError; end
83
+ class MissingAttachment < ResponseError; end
84
+ class MissingContentLength < ResponseError; end
85
+ class MissingRequestBodyError < ResponseError; end
86
+ class MissingSecurityElement < ResponseError; end
87
+ class MissingSecurityHeader < ResponseError; end
88
+ class NoLoggingStatusForKey < ResponseError; end
89
+ class NoSuchBucket < ResponseError; end
90
+ class NoSuchKey < ResponseError; end
91
+ class NotImplemented < ResponseError; end
92
+ class NotSignedUp < ResponseError; end
93
+ class OperationAborted < ResponseError; end
94
+ class PermanentRedirect < ResponseError; end
95
+ class PreconditionFailed < ResponseError; end
96
+ class Redirect < ResponseError; end
97
+ class RequestIsNotMultiPartContent < ResponseError; end
98
+ class RequestTimeout < ResponseError; end
99
+ class RequestTimeTooSkewed < ResponseError; end
100
+ class RequestTorrentOfBucketError < ResponseError; end
101
+ class SignatureDoesNotMatch < ResponseError; end
102
+ class SlowDown < ResponseError; end
103
+ class TemporaryRedirect < ResponseError; end
104
+ class TokenRefreshRequired < ResponseError; end
105
+ class TooManyBuckets < ResponseError; end
106
+ class UnexpectedContent < ResponseError; end
107
+ class UnresolvableGrantByEmailAddress < ResponseError; end
108
+ class UserKeyMustBeSpecified < ResponseError; end
109
+ end
110
+ end
data/lib/s3/object.rb ADDED
@@ -0,0 +1,251 @@
1
+ module S3
2
+
3
+ # Class responsible for handling objects stored in S3 buckets
4
+ class Object
5
+ include Parser
6
+ extend Forwardable
7
+
8
+ attr_accessor :content_type, :content_disposition, :content_encoding, :cache_control
9
+ attr_reader :last_modified, :etag, :size, :bucket, :key, :acl, :storage_class
10
+ attr_writer :content
11
+
12
+ def_instance_delegators :bucket, :name, :service, :bucket_request, :vhost?, :host, :path_prefix
13
+ def_instance_delegators :service, :protocol, :port, :secret_access_key
14
+ private_class_method :new
15
+
16
+ # Compares the object with other object. Returns true if the key
17
+ # of the objects are the same, and both have the same buckets (see
18
+ # Bucket equality)
19
+ def ==(other)
20
+ other.equal?(self) || (other.instance_of?(self.class) && self.key == other.key && self.bucket == other.bucket)
21
+ end
22
+
23
+ # Returns full key of the object: e.g. <tt>bucket-name/object/key.ext</tt>
24
+ def full_key
25
+ [name, key].join("/")
26
+ end
27
+
28
+ # Assigns a new +key+ to the object, raises ArgumentError if given
29
+ # key is not valid key name
30
+ def key=(key)
31
+ raise ArgumentError.new("Invalid key name: #{key}") unless key_valid?(key)
32
+ @key ||= key
33
+ end
34
+
35
+ # Assigns a new ACL to the object. Please note that ACL is not
36
+ # retrieved from the server and set to "public-read" by default.
37
+ #
38
+ # ==== Example
39
+ # object.acl = :public_read
40
+ def acl=(acl)
41
+ @acl = acl.to_s.gsub("_", "-") if acl
42
+ end
43
+
44
+ # Assigns a new storage class (RRS) to the object. Please note
45
+ # that the storage class is not retrieved from the server and set
46
+ # to "STANDARD" by default.
47
+ #
48
+ # ==== Example
49
+ # object.storage_class = :reduced_redundancy
50
+ def storage_class=(storage_class)
51
+ @storage_class = storage_class.to_s.upcase if storage_class
52
+ end
53
+
54
+ # Retrieves the object from the server. Method is used to download
55
+ # object information only (content type, size and so on). It does
56
+ # NOT download the content of the object (use the #content method
57
+ # to do it).
58
+ def retrieve
59
+ object_headers
60
+ self
61
+ end
62
+
63
+ # Retrieves the object from the server, returns true if the object
64
+ # exists or false otherwise. Uses #retrieve method, but catches
65
+ # S3::Error::NoSuchKey exception and returns false when it happens
66
+ def exists?
67
+ retrieve
68
+ true
69
+ rescue Error::NoSuchKey
70
+ false
71
+ end
72
+
73
+ # Download the content of the object, and caches it. Pass true
74
+ # to clear the cache and download the object again.
75
+ def content(reload = false)
76
+ get_object if reload or @content.nil?
77
+ @content
78
+ end
79
+
80
+ # Saves the object, returns true if successfull.
81
+ def save
82
+ put_object
83
+ true
84
+ end
85
+
86
+ # Copies the file to another key and/or bucket.
87
+ #
88
+ # ==== Options
89
+ # * <tt>:key</tt> - New key to store object in
90
+ # * <tt>:bucket</tt> - New bucket to store object in (instance of
91
+ # S3::Bucket)
92
+ # * <tt>:acl</tt> - ACL of the copied object (default:
93
+ # "public-read")
94
+ # * <tt>:content_type</tt> - Content type of the copied object
95
+ # (default: "application/octet-stream")
96
+ def copy(options = {})
97
+ copy_object(options)
98
+ end
99
+
100
+ # Destroys the file on the server
101
+ def destroy
102
+ delete_object
103
+ true
104
+ end
105
+
106
+ # Returns Object's URL using protocol specified in service,
107
+ # e.g. <tt>http://domain.com.s3.amazonaws.com/key/with/path.extension</tt>
108
+ def url
109
+ URI.escape("#{protocol}#{host}/#{path_prefix}#{key}")
110
+ end
111
+
112
+ # Returns a temporary url to the object that expires on the
113
+ # timestamp given. Defaults to one hour expire time.
114
+ def temporary_url(expires_at = Time.now + 3600)
115
+ signature = Signature.generate_temporary_url_signature(:bucket => name,
116
+ :resource => key,
117
+ :expires_at => expires_at,
118
+ :secret_access_key => secret_access_key)
119
+
120
+ "#{url}?AWSAccessKeyId=#{self.bucket.service.access_key_id}&Expires=#{expires_at.to_i.to_s}&Signature=#{signature}"
121
+ end
122
+
123
+ # Returns Object's CNAME URL (without <tt>s3.amazonaws.com</tt>
124
+ # suffix) using protocol specified in Service,
125
+ # e.g. <tt>http://domain.com/key/with/path.extension</tt>. (you
126
+ # have to set the CNAME in your DNS before using the CNAME URL
127
+ # schema).
128
+ def cname_url
129
+ URI.escape("#{protocol}#{name}/#{key}") if bucket.vhost?
130
+ end
131
+
132
+ def inspect #:nodoc:
133
+ "#<#{self.class}:/#{name}/#{key}>"
134
+ end
135
+
136
+ private
137
+
138
+ attr_writer :last_modified, :etag, :size, :original_key, :bucket
139
+
140
+ def copy_object(options = {})
141
+ key = options[:key] or raise ArgumentError, "No key given"
142
+ raise ArgumentError.new("Invalid key name: #{key}") unless key_valid?(key)
143
+ bucket = options[:bucket] || self.bucket
144
+
145
+ headers = {}
146
+
147
+ headers[:x_amz_acl] = options[:acl] || acl || "public-read"
148
+ headers[:content_type] = options[:content_type] || content_type || "application/octet-stream"
149
+ headers[:content_encoding] = options[:content_encoding] if options[:content_encoding]
150
+ headers[:content_disposition] = options[:content_disposition] if options[:content_disposition]
151
+ headers[:cache_control] = options[:cache_control] if options[:cache_control]
152
+ headers[:x_amz_copy_source] = full_key
153
+ headers[:x_amz_metadata_directive] = "REPLACE"
154
+ headers[:x_amz_copy_source_if_match] = options[:if_match] if options[:if_match]
155
+ headers[:x_amz_copy_source_if_none_match] = options[:if_none_match] if options[:if_none_match]
156
+ headers[:x_amz_copy_source_if_unmodified_since] = options[:if_modified_since] if options[:if_modified_since]
157
+ headers[:x_amz_copy_source_if_modified_since] = options[:if_unmodified_since] if options[:if_unmodified_since]
158
+
159
+ response = bucket.send(:bucket_request, :put, :path => key, :headers => headers)
160
+ object_attributes = parse_copy_object_result(response.body)
161
+
162
+ object = Object.send(:new, bucket, object_attributes.merge(:key => key, :size => size))
163
+ object.acl = response["x-amz-acl"]
164
+ object.content_type = response["content-type"]
165
+ object.content_encoding = response["content-encoding"]
166
+ object.content_disposition = response["content-disposition"]
167
+ object.cache_control = response["cache-control"]
168
+ object
169
+ end
170
+
171
+ def get_object(options = {})
172
+ response = object_request(:get, options)
173
+ parse_headers(response)
174
+ end
175
+
176
+ def object_headers(options = {})
177
+ response = object_request(:head, options)
178
+ parse_headers(response)
179
+ rescue Error::ResponseError => e
180
+ if e.response.code.to_i == 404
181
+ raise Error::ResponseError.exception("NoSuchKey").new("The specified key does not exist.", nil)
182
+ else
183
+ raise e
184
+ end
185
+ end
186
+
187
+ def put_object
188
+ response = object_request(:put, :body => content, :headers => dump_headers)
189
+ parse_headers(response)
190
+ end
191
+
192
+ def delete_object(options = {})
193
+ object_request(:delete)
194
+ end
195
+
196
+ def initialize(bucket, options = {})
197
+ self.bucket = bucket
198
+ self.key = options[:key]
199
+ self.last_modified = options[:last_modified]
200
+ self.etag = options[:etag]
201
+ self.size = options[:size]
202
+ self.cache_control = options[:cache_control]
203
+ end
204
+
205
+ def object_request(method, options = {})
206
+ bucket_request(method, options.merge(:path => key))
207
+ end
208
+
209
+ def last_modified=(last_modified)
210
+ @last_modified = Time.parse(last_modified) if last_modified
211
+ end
212
+
213
+ def etag=(etag)
214
+ @etag = etag[1..-2] if etag
215
+ end
216
+
217
+ def key_valid?(key)
218
+ if (key.nil? or key.empty? or key =~ %r#//#)
219
+ false
220
+ else
221
+ true
222
+ end
223
+ end
224
+
225
+ def dump_headers
226
+ headers = {}
227
+ headers[:x_amz_acl] = @acl || "public-read"
228
+ headers[:x_amz_storage_class] = @storage_class || "STANDARD"
229
+ headers[:content_type] = @content_type || "application/octet-stream"
230
+ headers[:content_encoding] = @content_encoding if @content_encoding
231
+ headers[:content_disposition] = @content_disposition if @content_disposition
232
+ headers[:cache_control] = @cache_control if @cache_control
233
+ headers
234
+ end
235
+
236
+ def parse_headers(response)
237
+ self.etag = response["etag"] if response.key?("etag")
238
+ self.content_type = response["content-type"] if response.key?("content-type")
239
+ self.content_disposition = response["content-disposition"] if response.key?("content-disposition")
240
+ self.cache_control = response["cache-control"] if response.key?("cache-control")
241
+ self.content_encoding = response["content-encoding"] if response.key?("content-encoding")
242
+ self.last_modified = response["last-modified"] if response.key?("last-modified")
243
+ if response.key?("content-range")
244
+ self.size = response["content-range"].sub(/[^\/]+\//, "").to_i
245
+ else
246
+ self.size = response["content-length"]
247
+ self.content = response.body
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,37 @@
1
+ module S3
2
+ module ObjectsExtension
3
+ # Builds the object in the bucket with given key
4
+ def build(key)
5
+ Object.send(:new, proxy_owner, :key => key)
6
+ end
7
+
8
+ # Finds first object with given name or raises the exception if
9
+ # not found
10
+ def find_first(name)
11
+ object = build(name)
12
+ object.retrieve
13
+ end
14
+ alias :find :find_first
15
+
16
+ # Finds the objects in the bucket.
17
+ #
18
+ # ==== Options
19
+ # * <tt>:prefix</tt> - Limits the response to keys which begin
20
+ # with the indicated prefix
21
+ # * <tt>:marker</tt> - Indicates where in the bucket to begin
22
+ # listing
23
+ # * <tt>:max_keys</tt> - The maximum number of keys you'd like
24
+ # to see
25
+ # * <tt>:delimiter</tt> - Causes keys that contain the same
26
+ # string between the prefix and the first occurrence of the
27
+ # delimiter to be rolled up into a single result element
28
+ def find_all(options = {})
29
+ proxy_owner.send(:list_bucket, options)
30
+ end
31
+
32
+ # Destroys all keys in the bucket
33
+ def destroy_all
34
+ proxy_target.each { |object| object.destroy }
35
+ end
36
+ end
37
+ end
data/lib/s3/parser.rb ADDED
@@ -0,0 +1,52 @@
1
+ module S3
2
+ module Parser
3
+ include REXML
4
+
5
+ def rexml_document(xml)
6
+ xml.force_encoding(Encoding::UTF_8) if xml.respond_to? :force_encoding
7
+ Document.new(xml)
8
+ end
9
+
10
+ def parse_list_all_my_buckets_result(xml)
11
+ names = []
12
+ rexml_document(xml).elements.each("ListAllMyBucketsResult/Buckets/Bucket/Name") { |e| names << e.text }
13
+ names
14
+ end
15
+
16
+ def parse_location_constraint(xml)
17
+ rexml_document(xml).elements["LocationConstraint"].text
18
+ end
19
+
20
+ def parse_list_bucket_result(xml)
21
+ objects_attributes = []
22
+ rexml_document(xml).elements.each("ListBucketResult/Contents") do |e|
23
+ object_attributes = {}
24
+ object_attributes[:key] = e.elements["Key"].text
25
+ object_attributes[:etag] = e.elements["ETag"].text
26
+ object_attributes[:last_modified] = e.elements["LastModified"].text
27
+ object_attributes[:size] = e.elements["Size"].text
28
+ objects_attributes << object_attributes
29
+ end
30
+ objects_attributes
31
+ end
32
+
33
+ def parse_copy_object_result(xml)
34
+ object_attributes = {}
35
+ document = rexml_document(xml)
36
+ object_attributes[:etag] = document.elements["CopyObjectResult/ETag"].text
37
+ object_attributes[:last_modified] = document.elements["CopyObjectResult/LastModified"].text
38
+ object_attributes
39
+ end
40
+
41
+ def parse_error(xml)
42
+ document = rexml_document(xml)
43
+ code = document.elements["Error/Code"].text
44
+ message = document.elements["Error/Message"].text
45
+ [code, message]
46
+ end
47
+
48
+ def parse_is_truncated xml
49
+ rexml_document(xml).elements["ListBucketResult/IsTruncated"].text =='true'
50
+ end
51
+ end
52
+ end