bare-ruby-aws 0.1
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/COPYING +340 -0
- data/INSTALL +260 -0
- data/NEWS +808 -0
- data/README +580 -0
- data/lib/amazon.rb +144 -0
- data/lib/amazon/aws.rb +963 -0
- data/lib/amazon/aws/cache.rb +141 -0
- data/lib/amazon/aws/search.rb +458 -0
- data/test/setup.rb +56 -0
- data/test/tc_amazon.rb +20 -0
- data/test/tc_aws.rb +160 -0
- data/test/tc_item_search.rb +105 -0
- data/test/tc_operation_request.rb +64 -0
- data/test/tc_serialisation.rb +107 -0
- data/test/ts_aws.rb +24 -0
- metadata +91 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
# $Id: cache.rb,v 1.8 2008/06/10 06:33:46 ianmacd Exp $
|
2
|
+
#
|
3
|
+
|
4
|
+
module Amazon
|
5
|
+
|
6
|
+
module AWS
|
7
|
+
|
8
|
+
# This class provides a simple results caching system for operations
|
9
|
+
# performed by AWS.
|
10
|
+
#
|
11
|
+
# To use it, set _cache_ to *true* in either <tt>/etc/amazonrc</tt> or
|
12
|
+
# <tt>~/.amazonrc</tt>.
|
13
|
+
#
|
14
|
+
# By default, the cache directory used is <tt>/tmp/amazon</tt>, but this
|
15
|
+
# can be changed by defining _cache_dir_ in either <tt>/etc/amazonrc</tt>
|
16
|
+
# or <tt>~/.amazonrc</tt>.
|
17
|
+
#
|
18
|
+
# When a cache is used, Ruby/AWS will check the cache directory for a
|
19
|
+
# recent copy of a response to the exact operation that you are
|
20
|
+
# performing. If found, the cached response will be returned instead of
|
21
|
+
# the request being forwarded to the AWS servers for processing. If no
|
22
|
+
# (recent) copy is found, the request will be forwarded to the AWS servers
|
23
|
+
# as usual. Recency is defined here as less than 24 hours old.
|
24
|
+
#
|
25
|
+
class Cache
|
26
|
+
|
27
|
+
require 'fileutils'
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'md5'
|
31
|
+
rescue LoadError
|
32
|
+
# Ruby 1.9 has moved MD5.
|
33
|
+
#
|
34
|
+
require 'digest/md5'
|
35
|
+
end
|
36
|
+
|
37
|
+
# Exception class for bad cache paths.
|
38
|
+
#
|
39
|
+
class PathError < StandardError; end
|
40
|
+
|
41
|
+
# Length of one day in seconds
|
42
|
+
#
|
43
|
+
ONE_DAY = 86400 # :nodoc:
|
44
|
+
|
45
|
+
# Age in days below which to consider cache files valid.
|
46
|
+
#
|
47
|
+
MAX_AGE = 1.0
|
48
|
+
|
49
|
+
# Default cache location.
|
50
|
+
#
|
51
|
+
DEFAULT_CACHE_DIR = '/tmp/amazon'
|
52
|
+
|
53
|
+
attr_reader :path
|
54
|
+
|
55
|
+
def initialize(path=DEFAULT_CACHE_DIR)
|
56
|
+
path ||= DEFAULT_CACHE_DIR
|
57
|
+
|
58
|
+
::FileUtils::mkdir_p( path ) unless File.exists? path
|
59
|
+
|
60
|
+
unless File.directory? path
|
61
|
+
raise PathError, "cache path #{path} is not a directory"
|
62
|
+
end
|
63
|
+
|
64
|
+
unless File.readable? path
|
65
|
+
raise PathError, "cache path #{path} is not readable"
|
66
|
+
end
|
67
|
+
|
68
|
+
unless File.writable? path
|
69
|
+
raise PathError, "cache path #{path} is not writable"
|
70
|
+
end
|
71
|
+
|
72
|
+
@path = path
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# Determine whether or not the the response to a given URL is cached.
|
77
|
+
# Returns *true* or *false*.
|
78
|
+
#
|
79
|
+
def cached?(url)
|
80
|
+
digest = Digest::MD5.hexdigest( url )
|
81
|
+
|
82
|
+
cache_files = Dir.glob( File.join( @path, '*' ) ).map do |d|
|
83
|
+
File.basename( d )
|
84
|
+
end
|
85
|
+
|
86
|
+
return cache_files.include?( digest ) &&
|
87
|
+
( Time.now - File.mtime( File.join( @path, digest ) ) ) /
|
88
|
+
ONE_DAY <= MAX_AGE
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Retrieve the cached response associated with _url_.
|
93
|
+
#
|
94
|
+
def fetch(url)
|
95
|
+
digest = Digest::MD5.hexdigest( url )
|
96
|
+
cache_file = File.join( @path, digest )
|
97
|
+
|
98
|
+
return nil unless File.exist? cache_file
|
99
|
+
|
100
|
+
Amazon.dprintf( 'Fetching %s from cache...', digest )
|
101
|
+
File.open( File.join( cache_file ) ).readlines.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# Cache the data from _contents_ and associate it with _url_.
|
106
|
+
#
|
107
|
+
def store(url, contents)
|
108
|
+
digest = Digest::MD5.hexdigest( url )
|
109
|
+
cache_file = File.join( @path, digest )
|
110
|
+
|
111
|
+
Amazon.dprintf( 'Caching %s...', digest )
|
112
|
+
File.open( cache_file, 'w' ) { |f| f.puts contents }
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# This method flushes all files from the cache directory specified
|
117
|
+
# in the object's <i>@path</i> variable.
|
118
|
+
#
|
119
|
+
def flush_all
|
120
|
+
FileUtils.rm Dir.glob( File.join( @path, '*' ) )
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# This method flushes expired files from the cache directory specified
|
125
|
+
# in the object's <i>@path</i> variable.
|
126
|
+
#
|
127
|
+
def flush_expired
|
128
|
+
now = Time.now
|
129
|
+
|
130
|
+
expired_files = Dir.glob( File.join( @path, '*' ) ).find_all do |f|
|
131
|
+
( now - File.mtime( f ) ) / ONE_DAY > MAX_AGE
|
132
|
+
end
|
133
|
+
|
134
|
+
FileUtils.rm expired_files
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
@@ -0,0 +1,458 @@
|
|
1
|
+
# $Id: search.rb,v 1.49 2010/03/19 19:28:19 ianmacd Exp $
|
2
|
+
#
|
3
|
+
|
4
|
+
module Amazon
|
5
|
+
|
6
|
+
module AWS
|
7
|
+
|
8
|
+
require 'amazon/aws'
|
9
|
+
require 'net/http'
|
10
|
+
require 'rexml/document'
|
11
|
+
require 'openssl'
|
12
|
+
|
13
|
+
# Load this library with:
|
14
|
+
#
|
15
|
+
# require 'amazon/aws/search'
|
16
|
+
#
|
17
|
+
module Search
|
18
|
+
|
19
|
+
class Request
|
20
|
+
|
21
|
+
include REXML
|
22
|
+
|
23
|
+
# Exception class for bad access key ID.
|
24
|
+
#
|
25
|
+
class AccessKeyIdError < Amazon::AWS::Error::AWSError; end
|
26
|
+
|
27
|
+
# Exception class for bad locales.
|
28
|
+
#
|
29
|
+
class LocaleError < Amazon::AWS::Error::AWSError; end
|
30
|
+
|
31
|
+
# Do we have support for the SHA-256 Secure Hash Algorithm?
|
32
|
+
#
|
33
|
+
# Note that Module#constants returns Strings in Ruby 1.8 and Symbols
|
34
|
+
# in 1.9.
|
35
|
+
#
|
36
|
+
DIGEST_SUPPORT = OpenSSL::Digest.constants.include?( 'SHA256' ) ||
|
37
|
+
OpenSSL::Digest.constants.include?( :SHA256 )
|
38
|
+
|
39
|
+
# Requests are authenticated using the SHA-256 Secure Hash Algorithm.
|
40
|
+
#
|
41
|
+
DIGEST = OpenSSL::Digest::Digest.new( 'sha256' ) if DIGEST_SUPPORT
|
42
|
+
|
43
|
+
attr_reader :conn, :config, :locale, :query, :user_agent
|
44
|
+
attr_writer :cache
|
45
|
+
attr_accessor :encoding
|
46
|
+
|
47
|
+
# This method is used to generate an AWS search request object.
|
48
|
+
#
|
49
|
+
# _key_id_ is your AWS {access key
|
50
|
+
# ID}[https://aws-portal.amazon.com/gp/aws/developer/registration/index.html].
|
51
|
+
# Note that your secret key, used for signing requests, can be
|
52
|
+
# specified only in your <tt>~/.amazonrc</tt> configuration file.
|
53
|
+
#
|
54
|
+
# _associate_ is your
|
55
|
+
# Associates[http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/GSG/BecominganAssociate.html]
|
56
|
+
# tag (if any), _locale_ is the locale in which you which to work
|
57
|
+
# (*us* for amazon.com[http://www.amazon.com/], *uk* for
|
58
|
+
# amazon.co.uk[http://www.amazon.co.uk], etc.), _cache_ is whether or
|
59
|
+
# not you wish to utilise a response cache, and _user_agent_ is the
|
60
|
+
# client name to pass when performing calls to AWS. By default,
|
61
|
+
# _user_agent_ will be set to a string identifying the Ruby/AWS
|
62
|
+
# library and its version number.
|
63
|
+
#
|
64
|
+
# _locale_ and _cache_ can also be set later, if you wish to change
|
65
|
+
# the current behaviour.
|
66
|
+
#
|
67
|
+
# Example:
|
68
|
+
#
|
69
|
+
# req = Request.new( '0Y44V8FAFNM119CX4TR2', 'calibanorg-20' )
|
70
|
+
#
|
71
|
+
def initialize(config=nil)
|
72
|
+
|
73
|
+
puts "Config: #{config}"
|
74
|
+
@config = Amazon::Config.new(config)
|
75
|
+
|
76
|
+
locale = @config['locale'] || 'us'
|
77
|
+
locale.downcase!
|
78
|
+
|
79
|
+
key_id = @config['key_id']
|
80
|
+
cache = @config['cache'] if cache.nil?
|
81
|
+
|
82
|
+
validate_locale( locale )
|
83
|
+
|
84
|
+
if key_id.nil?
|
85
|
+
raise AccessKeyIdError, 'key_id may not be nil'
|
86
|
+
end
|
87
|
+
|
88
|
+
@key_id = key_id
|
89
|
+
@tag = @config['associate'] || DEF_ASSOC[locale]
|
90
|
+
@user_agent = USER_AGENT
|
91
|
+
@cache = unless cache == 'false' || cache == false
|
92
|
+
Amazon::AWS::Cache.new( @config['cache_dir'] )
|
93
|
+
else
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
# Set the following two variables from the config file. Will be
|
98
|
+
# *nil* if not present in config file.
|
99
|
+
#
|
100
|
+
@api = @config['api']
|
101
|
+
@encoding = @config['encoding']
|
102
|
+
|
103
|
+
self.locale = locale
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# Assign a new locale. If the locale we're coming from is using the
|
108
|
+
# default Associate ID for that locale, then we use the new locale's
|
109
|
+
# default ID, too.
|
110
|
+
#
|
111
|
+
def locale=(l) # :nodoc:
|
112
|
+
old_locale = @locale ||= nil
|
113
|
+
@locale = validate_locale( l )
|
114
|
+
|
115
|
+
# Use the new locale's default ID if the ID currently in use is the
|
116
|
+
# current locale's default ID.
|
117
|
+
#
|
118
|
+
if @tag == Amazon::AWS::DEF_ASSOC[old_locale]
|
119
|
+
@tag = Amazon::AWS::DEF_ASSOC[@locale]
|
120
|
+
end
|
121
|
+
|
122
|
+
if @config.key?( @locale ) && @config[@locale].key?( 'associate' )
|
123
|
+
@tag = @config[@locale]['associate']
|
124
|
+
end
|
125
|
+
|
126
|
+
# We must now set up a new HTTP connection to the correct server for
|
127
|
+
# this locale, unless the same server is used for both.
|
128
|
+
#
|
129
|
+
unless Amazon::AWS::ENDPOINT[@locale] ==
|
130
|
+
Amazon::AWS::ENDPOINT[old_locale]
|
131
|
+
#connect( @locale )
|
132
|
+
@conn = nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
# If @cache has simply been assigned *true* at some point in time,
|
138
|
+
# assign a proper cache object to it when it is referenced. Otherwise,
|
139
|
+
# just return its value.
|
140
|
+
#
|
141
|
+
def cache # :nodoc:
|
142
|
+
if @cache == true
|
143
|
+
@cache = Amazon::AWS::Cache.new( @config['cache_dir'] )
|
144
|
+
else
|
145
|
+
@cache
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
# Verify the validity of a locale string. _l_ is the locale string.
|
151
|
+
#
|
152
|
+
def validate_locale(l)
|
153
|
+
unless Amazon::AWS::ENDPOINT.has_key? l
|
154
|
+
raise LocaleError, "invalid locale: #{l}"
|
155
|
+
end
|
156
|
+
l
|
157
|
+
end
|
158
|
+
private :validate_locale
|
159
|
+
|
160
|
+
|
161
|
+
# Return an HTTP connection for the current _locale_.
|
162
|
+
#
|
163
|
+
def connect(locale)
|
164
|
+
if ENV.key? 'http_proxy'
|
165
|
+
uri = URI.parse( ENV['http_proxy'] )
|
166
|
+
proxy_user = proxy_pass = nil
|
167
|
+
proxy_user, proxy_pass = uri.userinfo.split( /:/ ) if uri.userinfo
|
168
|
+
@conn = Net::HTTP::Proxy( uri.host, uri.port, proxy_user,
|
169
|
+
proxy_pass ).start(
|
170
|
+
Amazon::AWS::ENDPOINT[locale].host )
|
171
|
+
else
|
172
|
+
@conn = Net::HTTP::start( Amazon::AWS::ENDPOINT[locale].host )
|
173
|
+
end
|
174
|
+
end
|
175
|
+
private :connect
|
176
|
+
|
177
|
+
|
178
|
+
# Reconnect to the server if our connection has been lost (due to a
|
179
|
+
# time-out, etc.).
|
180
|
+
#
|
181
|
+
def reconnect # :nodoc:
|
182
|
+
connect( self.locale )
|
183
|
+
self
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
# This method checks for errors in an XML response returned by AWS.
|
188
|
+
# _xml_ is the XML node below which to search.
|
189
|
+
#
|
190
|
+
def error_check(xml)
|
191
|
+
if ! xml.nil? && xml = xml.elements['Errors/Error']
|
192
|
+
raise Amazon::AWS::Error.exception( xml )
|
193
|
+
end
|
194
|
+
end
|
195
|
+
private :error_check
|
196
|
+
|
197
|
+
|
198
|
+
# Add a timestamp to a request object's query string.
|
199
|
+
#
|
200
|
+
def timestamp # :nodoc:
|
201
|
+
@query << '&Timestamp=%s' %
|
202
|
+
[ Amazon.url_encode(
|
203
|
+
Time.now.utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) ) ]
|
204
|
+
end
|
205
|
+
private :timestamp
|
206
|
+
|
207
|
+
|
208
|
+
# Add a signature to a request object's query string. This implicitly
|
209
|
+
# also adds a timestamp.
|
210
|
+
#
|
211
|
+
def sign # :nodoc:
|
212
|
+
return false unless DIGEST_SUPPORT
|
213
|
+
|
214
|
+
timestamp
|
215
|
+
params = @query[1..-1].split( '&' ).sort.join( '&' )
|
216
|
+
|
217
|
+
sign_str = "GET\n%s\n%s\n%s" % [ ENDPOINT[@locale].host,
|
218
|
+
ENDPOINT[@locale].path,
|
219
|
+
params ]
|
220
|
+
|
221
|
+
Amazon.dprintf( 'Calculating SHA256 HMAC of "%s"...', sign_str )
|
222
|
+
|
223
|
+
hmac = OpenSSL::HMAC.digest( DIGEST,
|
224
|
+
@config['secret_key_id'],
|
225
|
+
sign_str )
|
226
|
+
Amazon.dprintf( 'SHA256 HMAC is "%s"', hmac.inspect )
|
227
|
+
|
228
|
+
base64_hmac = [ hmac ].pack( 'm' ).chomp
|
229
|
+
Amazon.dprintf( 'Base64-encoded HMAC is "%s".', base64_hmac )
|
230
|
+
|
231
|
+
signature = Amazon.url_encode( base64_hmac )
|
232
|
+
|
233
|
+
params << '&Signature=%s' % [ signature ]
|
234
|
+
@query = '?' + params
|
235
|
+
|
236
|
+
true
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
# Perform a search of the AWS database, returning an AWSObject.
|
241
|
+
#
|
242
|
+
# _operation_ is an object of a subclass of _Operation_, such as
|
243
|
+
# _ItemSearch_, _ItemLookup_, etc. It may also be a _MultipleOperation_
|
244
|
+
# object.
|
245
|
+
#
|
246
|
+
# In versions of Ruby/AWS up to prior to 0.8.0, the second parameter to
|
247
|
+
# this method was _response_group_. This way of passing response
|
248
|
+
# groups has been deprecated since 0.7.0 and completely removed in
|
249
|
+
# 0.8.0. To pair a set of response groups with an operation, assign
|
250
|
+
# directly to the operation's @response_group attribute.
|
251
|
+
#
|
252
|
+
# _nr_pages_ is the number of results pages to return. It defaults to
|
253
|
+
# <b>1</b>. If a higher number is given, pages 1 to _nr_pages_ will be
|
254
|
+
# returned. If the special value <b>:ALL_PAGES</b> is given, all
|
255
|
+
# results pages will be returned.
|
256
|
+
#
|
257
|
+
# Note that _ItemLookup_ operations can use several different
|
258
|
+
# pagination parameters. An _ItemLookup_ will typically return just
|
259
|
+
# one results page containing a single product, but <b>:ALL_PAGES</b>
|
260
|
+
# can still be used to apply the _OfferPage_ parameter to paginate
|
261
|
+
# through multiple pages of offers.
|
262
|
+
#
|
263
|
+
# Similarly, a single product may have multiple pages of reviews
|
264
|
+
# available. In such a case, it is up to the user to manually supply
|
265
|
+
# the _ReviewPage_ parameter and an appropriate value.
|
266
|
+
#
|
267
|
+
# In the same vein, variations can be returned by using the
|
268
|
+
# _VariationPage_ parameter.
|
269
|
+
#
|
270
|
+
# The pagination parameters supported by each type of operation,
|
271
|
+
# together with the maximum page number that can be retrieved for each
|
272
|
+
# type of data, are # documented in the AWS Developer's Guide:
|
273
|
+
#
|
274
|
+
# http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/DG/index.html?MaximumNumberofPages.html
|
275
|
+
#
|
276
|
+
# The pagination parameter used by <b>:ALL_PAGES</b> can be looked up
|
277
|
+
# in the Amazon::AWS::PAGINATION hash.
|
278
|
+
#
|
279
|
+
# If _operation_ is of class _MultipleOperation_, the operations
|
280
|
+
# encapsulated within will return only the first page of results,
|
281
|
+
# regardless of whether a higher number of pages is requested.
|
282
|
+
#
|
283
|
+
# If a block is passed to this method, each successive page of results
|
284
|
+
# will be yielded to the block.
|
285
|
+
#
|
286
|
+
def search(operation, nr_pages=1)
|
287
|
+
parameters = Amazon::AWS::SERVICE.
|
288
|
+
merge( { 'AWSAccessKeyId' => @key_id,
|
289
|
+
'AssociateTag' => @tag } ).
|
290
|
+
merge( operation.query_parameters )
|
291
|
+
|
292
|
+
if nr_pages.is_a? Amazon::AWS::ResponseGroup
|
293
|
+
raise ObsolescenceError, 'Request#search method no longer accepts response_group parameter.'
|
294
|
+
end
|
295
|
+
|
296
|
+
# Pre-0.8.0 user code may have passed *nil* as the second parameter,
|
297
|
+
# in order to use the @response_group of the operation.
|
298
|
+
#
|
299
|
+
nr_pages ||= 1
|
300
|
+
|
301
|
+
# Check to see whether a particular version of the API has been
|
302
|
+
# requested. If so, overwrite Version with the new value.
|
303
|
+
#
|
304
|
+
parameters.merge!( { 'Version' => @api } ) if @api
|
305
|
+
|
306
|
+
@query = Amazon::AWS.assemble_query( parameters, @encoding )
|
307
|
+
page = Amazon::AWS.get_page( self )
|
308
|
+
|
309
|
+
# Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
|
310
|
+
#
|
311
|
+
page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
|
312
|
+
|
313
|
+
doc = Document.new( page )
|
314
|
+
|
315
|
+
# Some errors occur at the very top level of the XML. For example,
|
316
|
+
# when no Operation parameter is given. This should not be possible
|
317
|
+
# with user code, but occurred during debugging of this library.
|
318
|
+
#
|
319
|
+
error_check( doc )
|
320
|
+
|
321
|
+
# Another possible error results in a document containing nothing
|
322
|
+
# but <Result>Internal Error</Result>. This occurs when a specific
|
323
|
+
# version of the AWS API is requested, in combination with an
|
324
|
+
# operation that did not yet exist in that version of the API.
|
325
|
+
#
|
326
|
+
# For example:
|
327
|
+
#
|
328
|
+
# http://ecs.amazonaws.com/onca/xml?AWSAccessKeyId=foo&Operation=VehicleSearch&Year=2008&ResponseGroup=VehicleMakes&Service=AWSECommerceService&Version=2008-03-03
|
329
|
+
#
|
330
|
+
if xml = doc.elements['Result']
|
331
|
+
raise Amazon::AWS::Error::AWSError, xml.text
|
332
|
+
end
|
333
|
+
|
334
|
+
# Fundamental errors happen at the OperationRequest level. For
|
335
|
+
# example, if an invalid AWSAccessKeyId is used.
|
336
|
+
#
|
337
|
+
error_check( doc.elements['*/OperationRequest'] )
|
338
|
+
|
339
|
+
# Check for parameter and value errors deeper down, inside Request.
|
340
|
+
#
|
341
|
+
if operation.kind == 'MultipleOperation'
|
342
|
+
|
343
|
+
# Everything is a level deeper, because of the
|
344
|
+
# <MultiOperationResponse> container.
|
345
|
+
#
|
346
|
+
# Check for errors in the first operation.
|
347
|
+
#
|
348
|
+
error_check( doc.elements['*/*/*/Request'] )
|
349
|
+
|
350
|
+
# Check for errors in the second operation.
|
351
|
+
#
|
352
|
+
error_check( doc.elements['*/*[3]/*/Request'] )
|
353
|
+
|
354
|
+
# If second operation is batched, check for errors in its 2nd set
|
355
|
+
# of results.
|
356
|
+
#
|
357
|
+
if batched = doc.elements['*/*[3]/*[2]/Request']
|
358
|
+
error_check( batched )
|
359
|
+
end
|
360
|
+
else
|
361
|
+
error_check( doc.elements['*/*/Request'] )
|
362
|
+
|
363
|
+
# If operation is batched, check for errors in its 2nd set of
|
364
|
+
# results.
|
365
|
+
#
|
366
|
+
if batched = doc.elements['*/*[3]/Request']
|
367
|
+
error_check( batched )
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
if doc.elements['*/*[2]/TotalPages']
|
372
|
+
total_pages = doc.elements['*/*[2]/TotalPages'].text.to_i
|
373
|
+
|
374
|
+
# FIXME: ListLookup and MultipleOperation (and possibly others) have
|
375
|
+
# TotalPages nested one level deeper. I should take some time to
|
376
|
+
# ensure that all operations that can return multiple results pages
|
377
|
+
# are covered by either the 'if' above or the 'elsif' here.
|
378
|
+
#
|
379
|
+
elsif doc.elements['*/*[2]/*[2]/TotalPages']
|
380
|
+
total_pages = doc.elements['*/*[2]/*[2]/TotalPages'].text.to_i
|
381
|
+
else
|
382
|
+
total_pages = 1
|
383
|
+
end
|
384
|
+
|
385
|
+
# Create a root AWS object and walk the XML response tree.
|
386
|
+
#
|
387
|
+
aws = AWS::AWSObject.new( operation )
|
388
|
+
aws.walk( doc )
|
389
|
+
result = aws
|
390
|
+
|
391
|
+
# If only one page has been requested or only one page is available,
|
392
|
+
# we can stop here. First yield to the block, if given.
|
393
|
+
#
|
394
|
+
if nr_pages == 1 || ( tp = total_pages ) == 1
|
395
|
+
yield result if block_given?
|
396
|
+
return result
|
397
|
+
end
|
398
|
+
|
399
|
+
# Limit the number of pages to the maximum number available.
|
400
|
+
#
|
401
|
+
nr_pages = tp.to_i if nr_pages == :ALL_PAGES || nr_pages > tp.to_i
|
402
|
+
|
403
|
+
if PAGINATION.key? operation.kind
|
404
|
+
page_parameter = PAGINATION[operation.kind]['parameter']
|
405
|
+
max_pages = PAGINATION[operation.kind]['max_page']
|
406
|
+
else
|
407
|
+
page_parameter = 'ItemPage'
|
408
|
+
max_pages = 400
|
409
|
+
end
|
410
|
+
|
411
|
+
# Iterate over pages 2 and higher, but go no higher than MAX_PAGES.
|
412
|
+
#
|
413
|
+
2.upto( nr_pages < max_pages ? nr_pages : max_pages ) do |page_nr|
|
414
|
+
@query = Amazon::AWS.assemble_query(
|
415
|
+
parameters.merge( { page_parameter => page_nr } ),
|
416
|
+
@encoding)
|
417
|
+
page = Amazon::AWS.get_page( self )
|
418
|
+
|
419
|
+
# Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
|
420
|
+
#
|
421
|
+
page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
|
422
|
+
|
423
|
+
doc = Document.new( page )
|
424
|
+
|
425
|
+
# Check for errors.
|
426
|
+
#
|
427
|
+
error_check( doc.elements['*/OperationRequest'] )
|
428
|
+
error_check( doc.elements['*/*/Request'] )
|
429
|
+
|
430
|
+
# Create a new AWS object and walk the XML response tree.
|
431
|
+
#
|
432
|
+
aws = AWS::AWSObject.new( operation )
|
433
|
+
aws.walk( doc )
|
434
|
+
|
435
|
+
# When dealing with multiple pages, we return not just an
|
436
|
+
# AWSObject, but an array of them.
|
437
|
+
#
|
438
|
+
result = [ result ] unless result.is_a? Array
|
439
|
+
|
440
|
+
# Append the new object to the array.
|
441
|
+
#
|
442
|
+
result << aws
|
443
|
+
end
|
444
|
+
|
445
|
+
# Yield each object to the block, if given.
|
446
|
+
#
|
447
|
+
result.each { |r| yield r } if block_given?
|
448
|
+
|
449
|
+
result
|
450
|
+
end
|
451
|
+
|
452
|
+
end
|
453
|
+
|
454
|
+
end
|
455
|
+
|
456
|
+
end
|
457
|
+
|
458
|
+
end
|