aws-sdk 1.2.3 → 1.2.4

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.
@@ -102,19 +102,28 @@ module AWS
102
102
 
103
103
  # Deletes the current bucket.
104
104
  #
105
- # @note the bucket *must* be empty.
105
+ # @note This operation will fail if the bucket is not empty.
106
+ #
106
107
  # @return [nil]
108
+ #
107
109
  def delete
108
110
  client.delete_bucket(:bucket_name => @name)
109
111
  nil
110
112
  end
111
113
 
112
- # Deletes any objects and versions which may be in the bucket,
113
- # then deletes the bucket.
114
+ # Attempts to delete all objects (or object versions) from the bucket
115
+ # and then attempts to delete the bucket.
116
+ #
117
+ # @note This operation may fail if you do not have privileges to delete
118
+ # all objects from the bucket.
119
+ #
114
120
  # @return [nil]
121
+ #
115
122
  def delete!
116
- versions.each{|version| version.delete }
117
- delete
123
+ versions.each_batch do |versions|
124
+ objects.delete(versions)
125
+ end
126
+ self.delete
118
127
  end
119
128
 
120
129
  # @return [String] bucket owner id
@@ -19,8 +19,6 @@ module AWS
19
19
  # @see PrefixedCollection
20
20
  class BucketVersionCollection
21
21
 
22
- include Core::Model
23
- include Enumerable
24
22
  include PrefixAndDelimiterCollection
25
23
 
26
24
  # @param [Bucket] bucket
@@ -35,14 +33,18 @@ module AWS
35
33
  # @return [ObjectVersion] Returns the most recently created object
36
34
  # version in the entire bucket.
37
35
  def latest
38
- self.find{|version| true }
36
+ first
37
+ #self.find{|version| true }
39
38
  end
40
39
 
41
40
  # Yields once for each version in the bucket.
42
41
  #
43
42
  # @yield [object_version]
43
+ #
44
44
  # @yieldparam [ObjectVersion] object_version
45
+ #
45
46
  # @return nil
47
+ #
46
48
  def each options = {}, █ super; end
47
49
 
48
50
  # @private
@@ -50,10 +52,8 @@ module AWS
50
52
  def each_member_in_page(page, &block)
51
53
  super
52
54
  page.versions.each do |version|
53
- object_version =
54
- ObjectVersion.new(bucket.objects[version.key],
55
- version.version_id,
56
- :delete_marker => version.delete_marker?)
55
+ object_version = ObjectVersion.new(bucket.objects[version.key],
56
+ version.version_id, :delete_marker => version.delete_marker?)
57
57
  yield(object_version)
58
58
  end
59
59
  end
@@ -72,10 +72,6 @@ module AWS
72
72
  protected
73
73
  def pagination_markers; super + [:version_id_marker]; end
74
74
 
75
- # @private
76
- protected
77
- def page_size(resp); super + resp.versions.size; end
78
-
79
75
  end
80
76
  end
81
77
  end
@@ -15,6 +15,7 @@ require 'rexml/document'
15
15
  require 'pathname'
16
16
  require 'stringio'
17
17
  require 'json'
18
+ require 'digest/md5'
18
19
 
19
20
  module AWS
20
21
  class S3
@@ -561,17 +562,19 @@ module AWS
561
562
  # marker.
562
563
  object_method(:get_object, :get,
563
564
  :header_options => {
564
- :range => "Range",
565
565
  :if_modified_since => "If-Modified-Since",
566
566
  :if_unmodified_since => "If-Unmodified-Since",
567
567
  :if_match => "If-Match",
568
568
  :if_none_match => "If-None-Match"
569
569
  }) do
570
570
  configure_request do |req, options|
571
+
571
572
  super(req, options)
573
+
572
574
  if options[:version_id]
573
575
  req.add_param('versionId', options[:version_id])
574
576
  end
577
+
575
578
  ["If-Modified-Since",
576
579
  "If-Unmodified-Since"].each do |date_header|
577
580
  case value = req.headers[date_header]
@@ -581,6 +584,13 @@ module AWS
581
584
  req.headers[date_header] = value.rfc2822
582
585
  end
583
586
  end
587
+
588
+ if options[:range]
589
+ range = options[:range]
590
+ range = "bytes=#{range.first}-#{range.last}" if range.is_a?(Range)
591
+ req.headers['Range'] = range
592
+ end
593
+
584
594
  end
585
595
 
586
596
  process_response do |resp|
@@ -705,6 +715,31 @@ module AWS
705
715
  end
706
716
  end
707
717
 
718
+ bucket_method(:delete_objects, :post, 'delete', XML::DeleteObjects) do
719
+ configure_request do |req, options|
720
+
721
+ super(req, options)
722
+
723
+ quiet = options.key?(:quiet) ? options[:quiet] : true
724
+
725
+ objects = options[:objects].inject('') do |xml,o|
726
+ xml << "<Object><Key>#{o[:key]}</Key>"
727
+ xml << "<VersionId>#{o[:version_id]}</VersionId>" if o[:version_id]
728
+ xml << "</Object>"
729
+ end
730
+
731
+ xml = '<?xml version="1.0" encoding="UTF-8"?>'
732
+ xml << "<Delete><Quiet>#{quiet}</Quiet>#{objects}</Delete>"
733
+
734
+ req.body = xml
735
+
736
+ md5 = Base64.encode64(Digest::MD5.digest(xml)).strip
737
+
738
+ req.headers['content-md5'] = md5
739
+
740
+ end
741
+ end
742
+
708
743
  object_method(:upload_part, :put,
709
744
  :header_options => {
710
745
  :content_md5 => 'Content-MD5'
@@ -799,6 +834,7 @@ module AWS
799
834
  object_method(:copy_object, :put,
800
835
  :header_options => {
801
836
  :copy_source => 'x-amz-copy-source',
837
+ :cache_control => 'Cache-Control',
802
838
  :metadata_directive => 'x-amz-metadata-directive',
803
839
  :storage_class => 'x-amz-storage-class',
804
840
  :server_side_encryption => 'x-amz-server-side-encryption',
@@ -159,10 +159,17 @@ module AWS
159
159
  element("StorageClass") { symbol_value }
160
160
  element("Initiated") { datetime_value }
161
161
  end
162
-
163
162
  include HasCommonPrefixes
164
163
  end
165
164
 
165
+ DeleteObjects = Core::XmlGrammar.customize do
166
+ element("Deleted") do
167
+ element("DeleteMarker") { boolean_value }
168
+ list
169
+ end
170
+ element("Error") { list; rename('errors') }
171
+ end
172
+
166
173
  # keep default behavior
167
174
  CompleteMultipartUpload = Core::XmlGrammar.customize
168
175
 
@@ -31,6 +31,20 @@ module AWS
31
31
  # @private
32
32
  module Errors
33
33
 
34
+ class BatchDeleteError < StandardError
35
+
36
+ def initialize error_counts
37
+ @error_counts = error_counts
38
+ total = error_counts.values.inject(0) {|sum,count| sum + count }
39
+ super("Failed to delete #{total} objects")
40
+ end
41
+
42
+ # @return [Hash] Returns a hash of error codes and how many
43
+ # objects failed with that code.
44
+ attr_reader :error_counts
45
+
46
+ end
47
+
34
48
  # This error is special, because S3 does not (and must not
35
49
  # according to RFC 2616) return a body with the HTTP response.
36
50
  # The interface is the same as for any other client error.
@@ -64,9 +64,6 @@ module AWS
64
64
  protected
65
65
  def pagination_markers; super + [:upload_id_marker]; end
66
66
 
67
- protected
68
- def page_size(resp); super + resp.uploads.size; end
69
-
70
67
  end
71
68
 
72
69
  end
@@ -102,6 +102,153 @@ module AWS
102
102
  super(prefix, mode)
103
103
  end
104
104
 
105
+ # Deletes the objects provided in as few requests as possible.
106
+ #
107
+ # # delete 2 objects (by key) in a single request
108
+ # bucket.objects.delete('abc', 'xyz')
109
+ #
110
+ # You can delete objects also by passing their S3Object representation:
111
+ #
112
+ # to_delete = []
113
+ # to_delete << buckets.objects['foo']
114
+ # to_delete << buckets.objects['bar']
115
+ #
116
+ # bucket.objects.delete(to_delete)
117
+ #
118
+ # @param [Mixed] objects One or more objects to delete. Each object
119
+ # can be one of the following:
120
+ #
121
+ # * An object key (string)
122
+ # * A hash with :key and :version_id (for versioned objects)
123
+ # * An {S3Object} instance
124
+ # * An {ObjectVersion} instance
125
+ #
126
+ # @raise [BatchDeleteError] If any of the objects failed to delete,
127
+ # a BatchDeleteError will be raised with a summary of the errors.
128
+ #
129
+ # @return [nil]
130
+ #
131
+ def delete *objects
132
+
133
+ objects = objects.flatten.collect do |obj|
134
+ case obj
135
+ when String then { :key => obj }
136
+ when Hash then obj
137
+ when S3Object then { :key => obj.key }
138
+ when ObjectVersion then { :key => obj.key, :version_id => obj.version_id }
139
+ else
140
+ msg = "objects must be keys (strings or hashes with :key and " +
141
+ ":version_id), S3Objects or ObjectVersions, got " +
142
+ object.class.name
143
+ raise ArgumentError, msg
144
+ end
145
+ end
146
+
147
+ batch_helper = BatchHelper.new(1000) do |batch|
148
+ client_opts = {}
149
+ client_opts[:bucket_name] = bucket.name
150
+ client_opts[:quiet] = true
151
+ client_opts[:objects] = batch
152
+ client.delete_objects(client_opts)
153
+ end
154
+
155
+ error_counts = {}
156
+ batch_helper.after_batch do |response|
157
+ response.errors.each do |error|
158
+ error_counts[error.code] ||= 0
159
+ error_counts[error.code] += 1
160
+ end
161
+ end
162
+
163
+ objects.each do |object|
164
+ batch_helper.add(object)
165
+ end
166
+
167
+ batch_helper.complete!
168
+
169
+ raise Errors::BatchDeleteError.new(error_counts) unless
170
+ error_counts.empty?
171
+
172
+ nil
173
+
174
+ end
175
+
176
+ # Deletes each object in the collection that returns a true value
177
+ # from block passed to this method. Deletes are batched for efficiency.
178
+ #
179
+ # # delete text files in the 2009 "folder"
180
+ # bucket.objects.with_prefix('2009/').delete_if {|o| o.key =~ /\.txt$/ }
181
+ #
182
+ # @yieldparam [S3Object] object
183
+ #
184
+ # @raise [BatchDeleteError] If any of the objects failed to delete,
185
+ # a BatchDeleteError will be raised with a summary of the errors.
186
+ #
187
+ def delete_if &block
188
+
189
+ error_counts = {}
190
+
191
+ batch_helper = BatchHelper.new(1000) do |objects|
192
+ begin
193
+ delete(objects)
194
+ rescue Errors::BatchDeleteError => error
195
+ error.error_counts.each_pair do |code,count|
196
+ error_counts[code] ||= 0
197
+ error_counts[code] += count
198
+ end
199
+ end
200
+ end
201
+
202
+ each do |object|
203
+ batch_helper.add(object) if yield(object)
204
+ end
205
+
206
+ batch_helper.complete!
207
+
208
+ raise Errors::BatchDeleteError.new(error_counts) unless
209
+ error_counts.empty?
210
+
211
+ nil
212
+
213
+ end
214
+
215
+ # Deletes all objects represented by this collection.
216
+ #
217
+ # @example Delete all objects from a bucket
218
+ #
219
+ # bucket.objects.delete_all
220
+ #
221
+ # @example Delete objects with a given prefix
222
+ #
223
+ # bucket.objects.with_prefix('2009/').delete_all
224
+ #
225
+ # @raise [BatchDeleteError] If any of the objects failed to delete,
226
+ # a BatchDeleteError will be raised with a summary of the errors.
227
+ #
228
+ # @return [Array] Returns an array of results
229
+ #
230
+ def delete_all
231
+
232
+ error_counts = {}
233
+
234
+ each_batch do |objects|
235
+ begin
236
+ delete(objects)
237
+ rescue Errors::BatchDeleteError => error
238
+ error.error_counts.each_pair do |code,count|
239
+ error_counts[code] ||= 0
240
+ error_counts[code] += count
241
+ end
242
+ end
243
+ end
244
+
245
+ raise Errors::BatchDeleteError.new(error_counts) unless
246
+ error_counts.empty?
247
+
248
+ nil
249
+
250
+ end
251
+
105
252
  # Iterates the collection, yielding instances of S3Object.
106
253
  #
107
254
  # Use break or raise an exception to terminate the enumeration.
@@ -127,7 +274,7 @@ module AWS
127
274
 
128
275
  # @private
129
276
  protected
130
- def list_request(options)
277
+ def list_request options
131
278
  client.list_objects(options)
132
279
  end
133
280
 
@@ -139,14 +286,43 @@ module AWS
139
286
 
140
287
  # @private
141
288
  protected
142
- def page_size resp
143
- super + resp.contents.size
289
+ def next_markers page
290
+ { :marker => (last = page.contents.last and last.key) }
144
291
  end
145
292
 
293
+ # processes items in batches of 1k items
146
294
  # @private
147
- protected
148
- def next_markers page
149
- { :marker => (last = page.contents.last and last.key) }
295
+ class BatchHelper
296
+
297
+ def initialize batch_size, &block
298
+ @batch_size = batch_size
299
+ @block = block
300
+ @batch = []
301
+ end
302
+
303
+ def after_batch &block
304
+ @after_batch = block
305
+ end
306
+
307
+ def add item
308
+ @batch << item
309
+ if @batch.size == @batch_size
310
+ process_batch
311
+ @batch = []
312
+ end
313
+ item
314
+ end
315
+
316
+ def complete!
317
+ process_batch unless @batch.empty?
318
+ end
319
+
320
+ protected
321
+ def process_batch
322
+ response = @block.call(@batch)
323
+ @after_batch.call(response) if @after_batch
324
+ end
325
+
150
326
  end
151
327
 
152
328
  end
@@ -17,47 +17,33 @@ module AWS
17
17
  # @private
18
18
  module PaginatedCollection
19
19
 
20
- def each(options = {}, &block)
21
- each_page(options) do |page|
22
- each_member_in_page(page, &block)
23
- end
24
- nil
25
- end
20
+ include Core::Collection::Limitable
26
21
 
27
22
  protected
28
- def each_member_in_page(page, &block); end
23
+ def _each_item markers, limit, options = {}, &block
24
+
25
+ options = list_options(options)
26
+ options.merge!(markers) unless markers.nil? or markers.empty?
27
+ options[limit_param] = limit || 1000
28
+
29
+ response = list_request(options)
30
+
31
+ each_member_in_page(response, &block)
32
+
33
+ response.truncated? ? next_markers(response) : nil
29
34
 
30
- protected
31
- def each_page(options = {}, &block)
32
- opts = list_options(options)
33
- limit = options[:limit]
34
- batch_size = options[:batch_size] || 1000
35
- markers = {}
36
- received = 0
37
-
38
- loop do
39
- page_opts = opts.dup
40
- page_opts.merge!(markers)
41
- page_opts[limit_param] =
42
- limit ? [limit - received, batch_size].min : batch_size
43
-
44
- page = list_request(page_opts)
45
- markers = next_markers(page)
46
- received += page_size(page)
47
-
48
- yield(page)
49
-
50
- return unless page.truncated?
51
- end
52
35
  end
53
36
 
37
+ protected
38
+ def each_member_in_page(page, &block); end
39
+
54
40
  protected
55
41
  def list_request(options)
56
42
  raise NotImplementedError
57
43
  end
58
44
 
59
45
  protected
60
- def list_options(options)
46
+ def list_options options
61
47
  opts = {}
62
48
  opts[:bucket_name] = bucket.name if respond_to?(:bucket)
63
49
  opts
@@ -74,20 +60,15 @@ module AWS
74
60
  end
75
61
 
76
62
  protected
77
- def next_markers(page)
78
- pagination_markers.inject({}) do |markers, marker|
79
- markers[marker] = page.send("next_#{marker}")
63
+ def next_markers page
64
+ pagination_markers.inject({}) do |markers, marker_name|
65
+ if marker = page.send("next_#{marker_name}")
66
+ markers[marker_name] = marker if marker
67
+ end
80
68
  markers
81
69
  end
82
70
  end
83
71
 
84
- protected
85
- def page_size(resp)
86
- (resp.respond_to?(:common_prefixes) and
87
- prefixes = resp.common_prefixes and
88
- prefixes.size) or 0
89
- end
90
-
91
72
  end
92
73
 
93
74
  end