s33r 0.2 → 0.3

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 (71) hide show
  1. data/bin/s3cli.rb +25 -16
  2. data/html/classes/MIME.html +120 -0
  3. data/html/classes/MIME/InvalidContentType.html +119 -0
  4. data/html/classes/MIME/Type.html +1173 -0
  5. data/html/classes/MIME/Types.html +566 -0
  6. data/html/classes/Net.html +108 -0
  7. data/html/classes/Net/HTTPGenericRequest.html +233 -0
  8. data/html/classes/Net/HTTPResponse.html +242 -0
  9. data/html/classes/S33r.html +743 -0
  10. data/html/classes/S33r/BucketListing.html +372 -0
  11. data/html/classes/S33r/Client.html +981 -0
  12. data/html/classes/S33r/NamedBucket.html +620 -0
  13. data/html/classes/S33r/S33rException.html +118 -0
  14. data/html/classes/S33r/S33rException/BucketListingMaxKeysError.html +111 -0
  15. data/html/classes/S33r/S33rException/InvalidBucketListing.html +111 -0
  16. data/html/classes/S33r/S33rException/MalformedBucketName.html +111 -0
  17. data/html/classes/S33r/S33rException/MethodNotAvailable.html +111 -0
  18. data/html/classes/S33r/S33rException/MissingRequiredHeaders.html +111 -0
  19. data/html/classes/S33r/S33rException/MissingResource.html +111 -0
  20. data/html/classes/S33r/S33rException/UnsupportedCannedACL.html +111 -0
  21. data/html/classes/S33r/S33rException/UnsupportedHTTPMethod.html +111 -0
  22. data/html/classes/S33r/S3Object.html +307 -0
  23. data/html/classes/S33r/S3User.html +171 -0
  24. data/html/classes/S33r/Sync.html +151 -0
  25. data/html/classes/XML.html +200 -0
  26. data/html/classes/XML/Document.html +125 -0
  27. data/html/classes/XML/Node.html +124 -0
  28. data/html/created.rid +1 -0
  29. data/html/files/CHANGELOG.html +101 -0
  30. data/html/files/MIT-LICENSE.html +129 -0
  31. data/html/files/README_txt.html +209 -0
  32. data/html/files/lib/s33r/bucket_listing_rb.html +116 -0
  33. data/html/files/lib/s33r/client_rb.html +110 -0
  34. data/html/files/lib/s33r/core_rb.html +113 -0
  35. data/html/files/lib/s33r/libxml_extensions_rb.html +107 -0
  36. data/html/files/lib/s33r/mimetypes_rb.html +120 -0
  37. data/html/files/lib/s33r/named_bucket_rb.html +101 -0
  38. data/html/files/lib/s33r/s33r_exception_rb.html +101 -0
  39. data/html/files/lib/s33r/s33r_http_rb.html +108 -0
  40. data/html/files/lib/s33r/sync_rb.html +101 -0
  41. data/html/files/lib/s33r_rb.html +101 -0
  42. data/html/fr_class_index.html +52 -0
  43. data/html/fr_file_index.html +39 -0
  44. data/html/fr_method_index.html +126 -0
  45. data/html/index.html +24 -0
  46. data/html/rdoc-style.css +208 -0
  47. data/lib/s33r/bucket_listing.rb +69 -60
  48. data/lib/s33r/client.rb +150 -73
  49. data/lib/s33r/core.rb +56 -44
  50. data/lib/s33r/libxml_extensions.rb +10 -5
  51. data/lib/s33r/mimetypes.rb +3 -2
  52. data/lib/s33r/named_bucket.rb +89 -24
  53. data/lib/s33r/{s3_exception.rb → s33r_exception.rb} +2 -2
  54. data/lib/s33r/{net_http_overrides.rb → s33r_http.rb} +29 -21
  55. data/lib/s33r/sync.rb +4 -2
  56. data/test/cases/spec_bucket_listing.rb +10 -13
  57. data/test/cases/spec_client.rb +65 -0
  58. data/test/cases/spec_core.rb +16 -11
  59. data/test/cases/spec_namedbucket.rb +32 -0
  60. data/test/cases/spec_sync.rb +6 -5
  61. data/test/cases/spec_xml.rb +1 -1
  62. data/test/files/client_config.yml +6 -0
  63. data/test/files/namedbucket_config.yml +12 -0
  64. data/test/{s3_test_constants.rb → test_setup.rb} +7 -6
  65. metadata +63 -11
  66. data/LICENSE.txt +0 -22
  67. data/MIT-LICENSE +0 -21
  68. data/README.txt +0 -19
  69. data/bin/config.yml +0 -5
  70. data/test/cases/unit_client.rb +0 -40
  71. data/test/cases/unit_named_bucket.rb +0 -12
@@ -1,35 +1,49 @@
1
- # use prefix to limit keys to some subset of all available keys;
2
- # use delimiter to group keys
1
+ # TODO: use prefix to limit keys to some subset of all available keys;
2
+ # TODO: use delimiter to group keys
3
3
 
4
4
  require 'date'
5
5
  require 'xml/libxml'
6
6
 
7
- module S3
7
+ module S33r
8
+ # Object representation of the content of a bucket.
8
9
  class BucketListing
9
- attr_reader :listing_xml
10
-
11
- # properties of the bucket are publically read-only;
12
- # can only be reset by resetting the listing_xml via listing_xml=
13
- # (which parses the bucket listing XML)
14
- attr_reader :name, :delimiter, :prefix, :marker, :max_keys, :is_truncated, :contents
15
-
16
- def initialize(bucket_listing_xml)
10
+ attr_reader :delimiter, :prefix, :marker, :max_keys, :is_truncated, :common_prefixes
11
+
12
+ # Name of the bucket this listing is for.
13
+ attr_reader :name
14
+ # Hash of objects in this bucket, keyed by their S3 keys.
15
+ attr_reader :contents
16
+ # A NamedBucket instance associated with this listing.
17
+ attr_accessor :named_bucket
18
+
19
+ # Create a new object representing a ListBucketResult.
20
+ #
21
+ # +bucket_listing_xml+ is a ListBucketResult document, as returned from a GET on a bucket
22
+ # (see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/).
23
+ #
24
+ # +named_bucket+ can be set to an existing NamedBucket instance, so that any objects
25
+ # inside this listing can be associated with that instance. This enables objects to be easily deleted
26
+ # without having to create a new Client instance.
27
+ def initialize(bucket_listing_xml, named_bucket=nil)
17
28
  @contents = {}
18
29
  @common_prefixes = {}
30
+ # the NamedBucket instance associated with this listing (if any)
31
+ @named_bucket = named_bucket
19
32
  set_listing_xml(bucket_listing_xml)
20
33
  end
21
34
 
35
+ # Convert a ListBucketResult XML document into an object representation.
22
36
  def set_listing_xml(bucket_listing_xml)
23
37
  # remove the namespace declaration: libxml doesn't like it
24
38
  bucket_listing_xml.gsub!(/ xmlns="http:\/\/s3.amazonaws.com\/doc\/2006-03-01\/"/, '')
25
- @listing_xml = bucket_listing_xml
26
39
  parse_listing(bucket_listing_xml)
27
40
  rescue
28
41
  message = "Cannot create bucket listing from supplied XML"
29
42
  message += " (was nil)" if bucket_listing_xml.nil?
30
- raise S3Exception::InvalidBucketListing, message
43
+ raise S33rException::InvalidBucketListing, message
31
44
  end
32
45
 
46
+ # Parse raw XML ListBucketResponse from S3 into object instances.
33
47
  def parse_listing(bucket_listing_xml)
34
48
  doc = XML.get_xml_doc(bucket_listing_xml)
35
49
 
@@ -48,43 +62,30 @@ module S3
48
62
 
49
63
  # contents
50
64
  doc.find('//Contents').to_a.each do |node|
51
- obj = S3Object.new
52
- obj.set_from_node(node)
65
+ obj = S3Object.new(node)
66
+ # Add to the content listing for the bucket
53
67
  @contents[obj.key] = obj
54
68
  end
55
69
  end
56
70
 
57
- # return an object in this bucket
71
+ # Return an object in this bucket by key.
58
72
  def [](key)
59
73
  @contents[key]
60
74
  end
75
+
76
+ # Pretty listing of keys in alphabetical order.
77
+ def pretty
78
+ @contents.keys.sort.each { |k| puts k }
79
+ end
61
80
 
62
- # setters
81
+ # Setters which perform some type casts and normalisation.
63
82
  private
64
- def name=(val)
65
- @name = string_prop_normalise(val)
66
- end
67
-
68
- def prefix=(val)
69
- @prefix = string_prop_normalise(val)
70
- end
71
-
72
- def delimiter=(val)
73
- @delimiter = string_prop_normalise(val)
74
- end
75
-
76
- def marker=(val)
77
- @marker = string_prop_normalise(val)
78
- end
79
-
80
- def max_keys=(val)
81
- @max_keys = val.to_i
82
- end
83
-
84
- def is_truncated=(val)
85
- @is_truncated = false
86
- @is_truncated = true if ('true' == val || true == val || 'True' == val)
87
- end
83
+ def name=(val); @name = string_prop_normalise(val); end
84
+ def prefix=(val); @prefix = string_prop_normalise(val); end
85
+ def delimiter=(val); @delimiter = string_prop_normalise(val); end
86
+ def marker=(val); @marker = string_prop_normalise(val); end
87
+ def max_keys=(val); @max_keys = val.to_i; end
88
+ def is_truncated=(val); @is_truncated = ('true' == val || true == val || 'True' == val); end
88
89
 
89
90
  # normalise string properties:
90
91
  # if value for XML element is nil, set property to empty string
@@ -95,44 +96,52 @@ module S3
95
96
 
96
97
  end
97
98
 
99
+ # Representation of an object stored in a bucket.
98
100
  class S3Object
99
- attr_reader :bucket_name
100
- attr_accessor :key, :last_modified, :etag, :size, :owner, :storage_class
101
-
102
- # bucket: NamedBucket which is the parent of this object
103
- def initialize(named_bucket=nil)
101
+ attr_reader :named_bucket, :key, :last_modified, :etag, :size, :owner, :storage_class
102
+ attr_writer :named_bucket
103
+
104
+ # Create from a node.
105
+ def initialize(node=nil, named_bucket=nil)
104
106
  @named_bucket = named_bucket
107
+ self.set_from_node(node) unless node.nil?
108
+ end
109
+
110
+ # Remove this object from associated NamedBucket.
111
+ def delete
112
+ @named_bucket.delete_key(@key) unless @named_bucket.nil?
105
113
  end
106
114
 
107
- # set properties from an XML string
108
- # if using this, should be an XML document containing a <Contents> element as root
115
+ # Set properties of the object from an XML string.
116
+ #
117
+ # +xml_str+ should be a string representing a full XML document,
118
+ # containing a <Contents> element as its root element.
109
119
  def set_from_xml_string(xml_str)
110
- doc = XML.get_xml_doc(xml_str)
111
- set_from_node(doc)
120
+ set_from_node(XML.get_xml_doc(xml_str))
112
121
  end
113
122
 
114
- # set properties from an XML document
115
- # XML::Document doc: XML document to parse
116
- # options:
117
- # :full => true: set all properties (default is to only set those in bucket listing)
118
- def set_from_node(doc, options={})
123
+ # Set properties of the object from an XML document.
124
+ #
125
+ # +doc+: XML::Document instance to parse to get properties for this object.
126
+ def set_from_node(doc)
119
127
  @key = doc.xget('Key')
120
128
  @last_modified = DateTime.parse(doc.xget('LastModified'))
121
129
  @etag = doc.xget('ETag').gsub("\"", "")
122
130
  @size = doc.xget('Size').to_i
123
131
  @owner = S3User.new(doc.find('Owner').to_a.first)
124
132
 
125
- if options[:full]
126
- # TODO: if setting from a full object listing (GET on a resource key),
127
- # do additional field setting here (e.g. x-amz-meta- headers)
128
- end
133
+ # TODO: if setting from a full object listing (GET on a resource key),
134
+ # do additional field setting here (e.g. x-amz-meta- headers)
135
+ # and assign the response body to some data field; detect whether
136
+ # these fields exist before attempting to set properties
129
137
  end
130
138
  end
131
139
 
132
140
  class S3User
133
141
  attr_accessor :id, :display_name
134
142
 
135
- # XML::Document owner_xml_doc: <Owner> node from inside ListBucketResult <Contents> element
143
+ # +owner_xml_doc+: XML::Document instance, representing an <Owner> node from
144
+ # inside a ListBucketResult <Contents> element (see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/).
136
145
  def initialize(owner_xml_doc)
137
146
  @id = owner_xml_doc.xget('//ID')
138
147
  @display_name = owner_xml_doc.xget('//DisplayName')
@@ -1,25 +1,36 @@
1
1
  require 'net/https'
2
2
  require 'cgi'
3
3
 
4
- module S3
4
+ module S33r
5
5
  include Net
6
6
 
7
- # the client actually performs operations over the network,
7
+ # The client performs operations over the network,
8
8
  # using the core to build request headers and content;
9
9
  # only client-specific headers are managed here: other headers
10
- # can be handled by the core
11
- # TODO: need to wrap XML returned into object representation
12
- # TODO: use customisable thread pool for requests
10
+ # can be handled by the core.
11
+ #--
12
+ # TODO: need to wrap XML returned into object representation.
13
+ # TODO: use customisable thread pool for requests.
14
+ #--
13
15
  class Client
14
- include S3
16
+ include S33r
15
17
 
16
- attr_accessor :chunk_size, :default_headers
17
-
18
- # options: hash of optional client config.
19
- # :ssl => false : only use plain HTTP
20
- # :dump_requests => true: dump each request's initial line and headers
18
+ attr_accessor :aws_access_key, :aws_secret_access_key
19
+
20
+ # Size of data chunk to be sent per request when putting data.
21
+ attr_accessor :chunk_size
22
+
23
+ # Headers which should be sent with every request by default (unless overridden).
24
+ attr_accessor :client_headers
25
+
26
+ # Configure either an SSL-enabled or plain HTTP client.
27
+ # (If using SSL, no verification of server certificate is performed.)
28
+ #
29
+ # +options+: hash of optional client config.:
30
+ # * <tt>:use_ssl => false</tt>: only use plain HTTP for connections
31
+ # * <tt>:dump_requests => true</tt>: dump each request's initial line and headers to STDOUT
21
32
  def initialize(aws_access_key, aws_secret_access_key, options={})
22
- if false == options[:ssl]
33
+ if false == options[:use_ssl]
23
34
  @client = HTTP.new(HOST, NON_SSL_PORT)
24
35
  @client.use_ssl = false
25
36
  else
@@ -31,8 +42,8 @@ module S3
31
42
 
32
43
  @dump_requests = (true == options[:dump_requests])
33
44
 
34
- # set default chunk size for streaming request body (1 Mb)
35
- @chunk_size = 1048576
45
+ # set default chunk size for streaming request body
46
+ @chunk_size = DEFAULT_CHUNK_SIZE
36
47
 
37
48
  # Amazon S3 developer keys
38
49
  @aws_access_key = aws_access_key
@@ -40,16 +51,51 @@ module S3
40
51
 
41
52
  # headers sent with every request made by this client
42
53
  @client_headers = {}
54
+ end
55
+
56
+ # Initialise client from YAML configuration file
57
+ # (see load_config method for details of acceptable format).
58
+ def Client.init(config_file)
59
+ aws_access_key, aws_secret_access_key, options, _ = load_config(config_file)
60
+ Client.new(aws_access_key, aws_secret_access_key, options)
61
+ end
62
+
63
+ # Load YAML config. file for a client. The config. file looks like this:
64
+ #
65
+ # :include: test/files/namedbucket_config.yml
66
+ #
67
+ # The +options+ section contains settings specific to Client and NamedClient instances; +custom+
68
+ # contains extra settings specific to your application.
69
+ # +options+ and +custom+ sections can be omitted, but settings for AWS keys are required.
70
+ #
71
+ # Returns an array <tt>[aws_access_key, aws_secret_access_key, options, custom]</tt>, where +options+
72
+ # and +custom+ are hashes.
73
+ def Client.load_config(config_file)
74
+ require 'yaml'
75
+ config = YAML::load_file(config_file)
76
+ aws_access_key = config['aws_access_key']
77
+ aws_secret_access_key = config['aws_secret_access_key']
43
78
 
44
- yield self if block_given?
79
+ options = {}
80
+ options = S33r.keys_to_symbols(config['options']) if config['options']
81
+
82
+ custom = {}
83
+ custom = S33r.keys_to_symbols(config['custom']) if config['custom']
84
+
85
+ [aws_access_key, aws_secret_access_key, options, custom]
45
86
  end
46
87
 
47
- # wrapper round embedded client use_ssl accessor
88
+ # Wrapper round embedded client +use_ssl+ accessor.
48
89
  def use_ssl?
49
90
  @client.use_ssl
50
91
  end
51
92
 
52
- # send a request over the wire
93
+ # Send a request over the wire.
94
+ #
95
+ # This method streams +data+ if it responds to the +stat+ method
96
+ # (as files do).
97
+ #
98
+ #-- TODO: set timeout on requests in case S3 is unresponsive
53
99
  def do_request(method, path, data=nil, headers={})
54
100
  req = get_requester(method, path)
55
101
  req.chunk_size = @chunk_size
@@ -89,92 +135,97 @@ module S3
89
135
 
90
136
  end
91
137
 
92
- # get
93
- def do_get(path='/', headers={})
94
- do_request('GET', path, headers)
95
- end
96
-
97
- # head
98
- def do_head(path='/', headers={})
99
- do_request('HEAD', path, nil, headers)
100
- end
101
-
102
- # post
103
- def do_post(path='/', data=nil, headers={})
104
- do_request('POST', path, data, headers)
105
- end
106
-
107
- # put
108
- def do_put(path='/', data=nil, headers={})
109
- do_request('PUT', path, data, headers)
110
- end
111
-
112
- # delete
113
- def do_delete(path, headers={})
114
- do_request('DELETE', path, nil, headers)
115
- end
116
-
117
- # return an instance of an appropriate request class
138
+ # Return an instance of an appropriate request class.
118
139
  def get_requester(method, path)
119
- raise S3Exception::UnsupportedHTTPMethod, "The #{method} HTTP method is not supported" if !(METHOD_VERBS.include?(method))
140
+ raise S33rException::UnsupportedHTTPMethod, "The #{method} HTTP method is not supported" if !(METHOD_VERBS.include?(method))
120
141
  eval("HTTP::" + method[0,1].upcase + method[1..-1].downcase + ".new('#{path}')")
121
142
  end
122
143
 
123
- # list all buckets
124
- def list_all_buckets
144
+ # List all buckets.
145
+ def list_buckets
125
146
  do_get('/')
126
147
  end
127
148
 
128
- # list entries in a bucket
129
- # query_params: hash of options on the bucket listing, passed as querystring parameters to S3
130
- # :prefix => 'some_string' : restrict results to keys beginning with 'some_string'
131
- # :marker => 'some_string' : restict results to keys occurring lexicographically after 'some_string'
132
- # :max_keys => 1000 : return at most this number of keys (maximum possible value is 1000)
133
- # :delimiter => 'some_string' :
149
+ # List entries in a bucket.
150
+ #
151
+ # +query_params+: hash of options on the bucket listing request, passed as querystring parameters to S3
152
+ # (see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/).
153
+ # * <tt>:prefix => 'some_string'</tt>: restrict results to keys beginning with 'some_string'
154
+ # * <tt>:marker => 'some_string'</tt>: restict results to keys occurring lexicographically after 'some_string'
155
+ # * <tt>:max_keys => 1000</tt>: return at most this number of keys (maximum possible value is 1000)
156
+ # * <tt>:delimiter => 'some_string'</tt>: keys containing the same string between prefix and the delimiter
157
+ # are rolled up into a CommonPrefixes element inside the response
134
158
  def list_bucket(bucket_name, query_params={})
135
159
  if query_params[:max_keys]
136
160
  max_keys = query_params[:max_keys].to_i
137
- raise S3Exception::BucketListingMaxKeysError, "max_keys option to list bucket cannot be > #{BUCKET_LIST_MAX_MAX_KEYS}" \
161
+ raise S33rException::BucketListingMaxKeysError, "max_keys option to list bucket cannot be > #{BUCKET_LIST_MAX_MAX_KEYS}" \
138
162
  if max_keys > BUCKET_LIST_MAX_MAX_KEYS
139
163
 
140
164
  # take out the max_keys parameter and move it to max-keys
141
165
  query_params['max-keys'] = query_params.delete(:max_keys)
142
166
  end
143
- do_get("/#{bucket_name}" + generate_querystring(query_params))
167
+
168
+ resp = do_get("/#{bucket_name}" + generate_querystring(query_params))
169
+ bucket_listing = BucketListing.new(resp.body)
170
+
171
+ [resp, bucket_listing]
144
172
  end
145
173
 
146
- # create a bucket
174
+ # Create a bucket.
147
175
  def create_bucket(bucket_name, headers={})
148
176
  do_put("/#{bucket_name}", nil, headers)
149
177
  end
150
178
 
151
- # delete a bucket
152
- # TODO: enable deletion of keys inside the bucket
153
- # TODO: maybe delete keys matching a partial path
154
- def delete_bucket(bucket_name, headers={})
179
+ # Delete a bucket.
180
+ #
181
+ # +options+ hash can contain the following:
182
+ # * <tt>:force => true</tt>: delete all keys within the bucket then delete the bucket itself
183
+ #-- TODO: maybe delete keys matching a partial path
184
+ def delete_bucket(bucket_name, headers={}, options={})
185
+ if true == options[:force]
186
+ _, bucket_listing = list_bucket(bucket_name)
187
+ bucket_listing.contents.each_value do |obj|
188
+ delete_resource(bucket_name, obj.key)
189
+ end
190
+ end
191
+
155
192
  do_delete("/#{bucket_name}", headers)
156
193
  end
157
194
 
158
- # return true if bucket exists
195
+ # Returns true if bucket exists.
159
196
  def bucket_exists?(bucket_name)
160
- list_bucket(bucket_name).ok?
197
+ do_head("/#{bucket_name}").ok?
198
+ end
199
+
200
+ # Fetch a resource and return a fleshed-out S3Object instance.
201
+ def get_resource(bucket_name, resource_key, headers={})
202
+ do_get("/#{bucket_name}/#{resource_key}", headers)
161
203
  end
162
204
 
163
- # put some generic resource onto S3
205
+ # Put some generic resource onto S3.
164
206
  def put_resource(bucket_name, resource_key, data, headers={})
165
207
  do_put("/#{bucket_name}/" + "#{CGI::escape(resource_key)}", data, headers)
166
208
  end
167
209
 
168
- # put a string onto S3
210
+ # Put a string onto S3.
169
211
  def put_text(string, bucket_name, resource_key, headers={})
170
212
  headers["Content-Type"] = "text/plain"
171
213
  put_resource(bucket_name, resource_key, string, headers)
172
214
  end
173
215
 
174
- # put a file onto S3
175
- # options: to simplify setting of some headers with specific meaning to S3
176
- # :render_as_attachment => true: set the Content-Disposition for this file to "attachment" and set
177
- # the default filename for saving the file (when accessed by a web browser) to _filename_
216
+ # Put a file onto S3.
217
+ #
218
+ # If +resource_key+ is nil, the filename is used as the key instead.
219
+ #
220
+ # +headers+ sets some headers with the request; useful if you have an odd file type
221
+ # not recognised by the mimetypes library, and want to explicitly set the Content-Type header.
222
+ #
223
+ # +options+ hash simplifies setting some headers with specific meaning to S3:
224
+ # * <tt>:render_as_attachment => true</tt>: set the Content-Disposition for this file to "attachment" and set
225
+ # the default filename for saving the file (when accessed by a web browser) to +filename+; this
226
+ # turns the file into a download when opened in a browser, rather than trying to render it inline.
227
+ #
228
+ # Note that this method uses a handle to the file, so it can be streamed in chunks to S3.
178
229
  def put_file(filename, bucket_name, resource_key=nil, headers={}, options={})
179
230
  # default to the file path as the resource key if none explicitly set
180
231
  if resource_key.nil?
@@ -186,7 +237,7 @@ module S3
186
237
  headers['Content-Disposition'] = "attachment; filename=#{File.basename(filename)}"
187
238
  end
188
239
 
189
- # content type is explicitly set in the headers
240
+ # content type is explicitly set in the headers, so apply to request
190
241
  if headers[:content_type]
191
242
  # use the first MIME type corresponding to this content type string
192
243
  # (MIME::Types returns an array of possible MIME types)
@@ -205,14 +256,40 @@ module S3
205
256
  end
206
257
  end
207
258
 
208
- # TODO: delete resource by bucket and key
209
- def delete_resource(bucket_name, resource_key)
259
+ # Delete a resource from S3.
260
+ def delete_resource(bucket_name, resource_key, headers={})
261
+ do_delete("/#{bucket_name}/#{resource_key}", headers)
210
262
  end
211
263
 
212
- # add any default headers which should be sent with every request from the client;
213
- # any headers passed into this method override the defaults in @client_headers
264
+ # Add any default headers which should be sent with every request from the client.
265
+ #
266
+ # +headers+ is a hash of headers already set up. Any headers passed in here
267
+ # override the defaults in +client_headers+.
268
+ #
269
+ # Returns +headers+ with the content of +client_headers+ merged in.
214
270
  def add_client_headers(headers)
215
- headers.merge!(@client_headers) { |key, arg, default| arg }
271
+ headers.merge!(client_headers) { |key, arg, default| arg }
272
+ end
273
+
274
+ protected
275
+ def do_get(path='/', headers={})
276
+ do_request('GET', path, headers)
277
+ end
278
+
279
+ def do_head(path='/', headers={})
280
+ do_request('HEAD', path, nil, headers)
281
+ end
282
+
283
+ def do_post(path='/', data=nil, headers={})
284
+ do_request('POST', path, data, headers)
285
+ end
286
+
287
+ def do_put(path='/', data=nil, headers={})
288
+ do_request('PUT', path, data, headers)
289
+ end
290
+
291
+ def do_delete(path, headers={})
292
+ do_request('DELETE', path, nil, headers)
216
293
  end
217
294
 
218
295
  end