papercavalier-ruby-aaws 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/COPYING +340 -0
- data/INSTALL +260 -0
- data/NEWS +808 -0
- data/README +679 -0
- data/README.rdoc +140 -0
- data/Rakefile +17 -0
- data/VERSION.yml +5 -0
- data/example/batch_operation +28 -0
- data/example/browse_node_lookup1 +46 -0
- data/example/customer_content_lookup1 +27 -0
- data/example/customer_content_search1 +21 -0
- data/example/example1 +78 -0
- data/example/help1 +24 -0
- data/example/item_lookup1 +56 -0
- data/example/item_lookup2 +56 -0
- data/example/item_search1 +30 -0
- data/example/item_search2 +37 -0
- data/example/item_search3 +23 -0
- data/example/list_lookup1 +29 -0
- data/example/list_search1 +30 -0
- data/example/multiple_operation1 +69 -0
- data/example/seller_listing_lookup1 +30 -0
- data/example/seller_listing_search1 +29 -0
- data/example/seller_lookup1 +45 -0
- data/example/shopping_cart1 +42 -0
- data/example/similarity_lookup1 +48 -0
- data/example/tag_lookup1 +34 -0
- data/example/transaction_lookup1 +25 -0
- data/example/vehicle_search +22 -0
- data/lib/amazon.rb +165 -0
- data/lib/amazon/aws.rb +1493 -0
- data/lib/amazon/aws/cache.rb +141 -0
- data/lib/amazon/aws/search.rb +464 -0
- data/lib/amazon/aws/shoppingcart.rb +537 -0
- data/lib/amazon/locale.rb +102 -0
- data/test/setup.rb +56 -0
- data/test/tc_amazon.rb +20 -0
- data/test/tc_aws.rb +160 -0
- data/test/tc_browse_node_lookup.rb +49 -0
- data/test/tc_customer_content_lookup.rb +49 -0
- data/test/tc_help.rb +44 -0
- data/test/tc_item_lookup.rb +47 -0
- data/test/tc_item_search.rb +105 -0
- data/test/tc_list_lookup.rb +60 -0
- data/test/tc_list_search.rb +44 -0
- data/test/tc_multiple_operation.rb +375 -0
- data/test/tc_operation_request.rb +64 -0
- data/test/tc_seller_listing_lookup.rb +47 -0
- data/test/tc_seller_listing_search.rb +55 -0
- data/test/tc_seller_lookup.rb +44 -0
- data/test/tc_serialisation.rb +107 -0
- data/test/tc_shopping_cart.rb +214 -0
- data/test/tc_similarity_lookup.rb +48 -0
- data/test/tc_tag_lookup.rb +24 -0
- data/test/tc_transaction_lookup.rb +24 -0
- data/test/tc_vehicle_operations.rb +118 -0
- data/test/ts_aws.rb +24 -0
- metadata +141 -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,464 @@
|
|
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(key_id=nil, associate=nil, locale=nil, cache=nil,
|
72
|
+
user_agent=USER_AGENT)
|
73
|
+
|
74
|
+
@config ||= Amazon::Config.new
|
75
|
+
|
76
|
+
def_locale = locale
|
77
|
+
locale = 'us' unless locale
|
78
|
+
locale.downcase!
|
79
|
+
|
80
|
+
key_id ||= @config['key_id']
|
81
|
+
cache = @config['cache'] if cache.nil?
|
82
|
+
|
83
|
+
# Take locale from config file if no locale was passed to method.
|
84
|
+
#
|
85
|
+
if @config.key?( 'locale' ) && ! def_locale
|
86
|
+
locale = @config['locale']
|
87
|
+
end
|
88
|
+
validate_locale( locale )
|
89
|
+
|
90
|
+
if key_id.nil?
|
91
|
+
raise AccessKeyIdError, 'key_id may not be nil'
|
92
|
+
end
|
93
|
+
|
94
|
+
@key_id = key_id
|
95
|
+
@tag = associate || @config['associate'] || DEF_ASSOC[locale]
|
96
|
+
@user_agent = user_agent
|
97
|
+
@cache = unless cache == 'false' || cache == false
|
98
|
+
Amazon::AWS::Cache.new( @config['cache_dir'] )
|
99
|
+
else
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# Set the following two variables from the config file. Will be
|
104
|
+
# *nil* if not present in config file.
|
105
|
+
#
|
106
|
+
@api = @config['api']
|
107
|
+
@encoding = @config['encoding']
|
108
|
+
|
109
|
+
self.locale = locale
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# Assign a new locale. If the locale we're coming from is using the
|
114
|
+
# default Associate ID for that locale, then we use the new locale's
|
115
|
+
# default ID, too.
|
116
|
+
#
|
117
|
+
def locale=(l) # :nodoc:
|
118
|
+
old_locale = @locale ||= nil
|
119
|
+
@locale = validate_locale( l )
|
120
|
+
|
121
|
+
# Use the new locale's default ID if the ID currently in use is the
|
122
|
+
# current locale's default ID.
|
123
|
+
#
|
124
|
+
if @tag == Amazon::AWS::DEF_ASSOC[old_locale]
|
125
|
+
@tag = Amazon::AWS::DEF_ASSOC[@locale]
|
126
|
+
end
|
127
|
+
|
128
|
+
if @config.key?( @locale ) && @config[@locale].key?( 'associate' )
|
129
|
+
@tag = @config[@locale]['associate']
|
130
|
+
end
|
131
|
+
|
132
|
+
# We must now set up a new HTTP connection to the correct server for
|
133
|
+
# this locale, unless the same server is used for both.
|
134
|
+
#
|
135
|
+
unless Amazon::AWS::ENDPOINT[@locale] ==
|
136
|
+
Amazon::AWS::ENDPOINT[old_locale]
|
137
|
+
#connect( @locale )
|
138
|
+
@conn = nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
# If @cache has simply been assigned *true* at some point in time,
|
144
|
+
# assign a proper cache object to it when it is referenced. Otherwise,
|
145
|
+
# just return its value.
|
146
|
+
#
|
147
|
+
def cache # :nodoc:
|
148
|
+
if @cache == true
|
149
|
+
@cache = Amazon::AWS::Cache.new( @config['cache_dir'] )
|
150
|
+
else
|
151
|
+
@cache
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
# Verify the validity of a locale string. _l_ is the locale string.
|
157
|
+
#
|
158
|
+
def validate_locale(l)
|
159
|
+
unless Amazon::AWS::ENDPOINT.has_key? l
|
160
|
+
raise LocaleError, "invalid locale: #{l}"
|
161
|
+
end
|
162
|
+
l
|
163
|
+
end
|
164
|
+
private :validate_locale
|
165
|
+
|
166
|
+
|
167
|
+
# Return an HTTP connection for the current _locale_.
|
168
|
+
#
|
169
|
+
def connect(locale)
|
170
|
+
if ENV.key? 'http_proxy'
|
171
|
+
uri = URI.parse( ENV['http_proxy'] )
|
172
|
+
proxy_user = proxy_pass = nil
|
173
|
+
proxy_user, proxy_pass = uri.userinfo.split( /:/ ) if uri.userinfo
|
174
|
+
@conn = Net::HTTP::Proxy( uri.host, uri.port, proxy_user,
|
175
|
+
proxy_pass ).start(
|
176
|
+
Amazon::AWS::ENDPOINT[locale].host )
|
177
|
+
else
|
178
|
+
@conn = Net::HTTP::start( Amazon::AWS::ENDPOINT[locale].host )
|
179
|
+
end
|
180
|
+
end
|
181
|
+
private :connect
|
182
|
+
|
183
|
+
|
184
|
+
# Reconnect to the server if our connection has been lost (due to a
|
185
|
+
# time-out, etc.).
|
186
|
+
#
|
187
|
+
def reconnect # :nodoc:
|
188
|
+
connect( self.locale )
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# This method checks for errors in an XML response returned by AWS.
|
194
|
+
# _xml_ is the XML node below which to search.
|
195
|
+
#
|
196
|
+
def error_check(xml)
|
197
|
+
if ! xml.nil? && xml = xml.elements['Errors/Error']
|
198
|
+
raise Amazon::AWS::Error.exception( xml )
|
199
|
+
end
|
200
|
+
end
|
201
|
+
private :error_check
|
202
|
+
|
203
|
+
|
204
|
+
# Add a timestamp to a request object's query string.
|
205
|
+
#
|
206
|
+
def timestamp # :nodoc:
|
207
|
+
@query << '&Timestamp=%s' %
|
208
|
+
[ Amazon.url_encode(
|
209
|
+
Time.now.utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) ) ]
|
210
|
+
end
|
211
|
+
private :timestamp
|
212
|
+
|
213
|
+
|
214
|
+
# Add a signature to a request object's query string. This implicitly
|
215
|
+
# also adds a timestamp.
|
216
|
+
#
|
217
|
+
def sign # :nodoc:
|
218
|
+
return false unless DIGEST_SUPPORT
|
219
|
+
|
220
|
+
timestamp
|
221
|
+
params = @query[1..-1].split( '&' ).sort.join( '&' )
|
222
|
+
|
223
|
+
sign_str = "GET\n%s\n%s\n%s" % [ ENDPOINT[@locale].host,
|
224
|
+
ENDPOINT[@locale].path,
|
225
|
+
params ]
|
226
|
+
|
227
|
+
Amazon.dprintf( 'Calculating SHA256 HMAC of "%s"...', sign_str )
|
228
|
+
|
229
|
+
hmac = OpenSSL::HMAC.digest( DIGEST,
|
230
|
+
@config['secret_key_id'],
|
231
|
+
sign_str )
|
232
|
+
Amazon.dprintf( 'SHA256 HMAC is "%s"', hmac.inspect )
|
233
|
+
|
234
|
+
base64_hmac = [ hmac ].pack( 'm' ).chomp
|
235
|
+
Amazon.dprintf( 'Base64-encoded HMAC is "%s".', base64_hmac )
|
236
|
+
|
237
|
+
signature = Amazon.url_encode( base64_hmac )
|
238
|
+
|
239
|
+
params << '&Signature=%s' % [ signature ]
|
240
|
+
@query = '?' + params
|
241
|
+
|
242
|
+
true
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
# Perform a search of the AWS database, returning an AWSObject.
|
247
|
+
#
|
248
|
+
# _operation_ is an object of a subclass of _Operation_, such as
|
249
|
+
# _ItemSearch_, _ItemLookup_, etc. It may also be a _MultipleOperation_
|
250
|
+
# object.
|
251
|
+
#
|
252
|
+
# In versions of Ruby/AWS up to prior to 0.8.0, the second parameter to
|
253
|
+
# this method was _response_group_. This way of passing response
|
254
|
+
# groups has been deprecated since 0.7.0 and completely removed in
|
255
|
+
# 0.8.0. To pair a set of response groups with an operation, assign
|
256
|
+
# directly to the operation's @response_group attribute.
|
257
|
+
#
|
258
|
+
# _nr_pages_ is the number of results pages to return. It defaults to
|
259
|
+
# <b>1</b>. If a higher number is given, pages 1 to _nr_pages_ will be
|
260
|
+
# returned. If the special value <b>:ALL_PAGES</b> is given, all
|
261
|
+
# results pages will be returned.
|
262
|
+
#
|
263
|
+
# Note that _ItemLookup_ operations can use several different
|
264
|
+
# pagination parameters. An _ItemLookup_ will typically return just
|
265
|
+
# one results page containing a single product, but <b>:ALL_PAGES</b>
|
266
|
+
# can still be used to apply the _OfferPage_ parameter to paginate
|
267
|
+
# through multiple pages of offers.
|
268
|
+
#
|
269
|
+
# Similarly, a single product may have multiple pages of reviews
|
270
|
+
# available. In such a case, it is up to the user to manually supply
|
271
|
+
# the _ReviewPage_ parameter and an appropriate value.
|
272
|
+
#
|
273
|
+
# In the same vein, variations can be returned by using the
|
274
|
+
# _VariationPage_ parameter.
|
275
|
+
#
|
276
|
+
# The pagination parameters supported by each type of operation,
|
277
|
+
# together with the maximum page number that can be retrieved for each
|
278
|
+
# type of data, are # documented in the AWS Developer's Guide:
|
279
|
+
#
|
280
|
+
# http://docs.amazonwebservices.com/AWSECommerceService/2009-11-01/DG/index.html?MaximumNumberofPages.html
|
281
|
+
#
|
282
|
+
# The pagination parameter used by <b>:ALL_PAGES</b> can be looked up
|
283
|
+
# in the Amazon::AWS::PAGINATION hash.
|
284
|
+
#
|
285
|
+
# If _operation_ is of class _MultipleOperation_, the operations
|
286
|
+
# encapsulated within will return only the first page of results,
|
287
|
+
# regardless of whether a higher number of pages is requested.
|
288
|
+
#
|
289
|
+
# If a block is passed to this method, each successive page of results
|
290
|
+
# will be yielded to the block.
|
291
|
+
#
|
292
|
+
def search(operation, nr_pages=1)
|
293
|
+
parameters = Amazon::AWS::SERVICE.
|
294
|
+
merge( { 'AWSAccessKeyId' => @key_id,
|
295
|
+
'AssociateTag' => @tag } ).
|
296
|
+
merge( operation.query_parameters )
|
297
|
+
|
298
|
+
if nr_pages.is_a? Amazon::AWS::ResponseGroup
|
299
|
+
raise ObsolescenceError, 'Request#search method no longer accepts response_group parameter.'
|
300
|
+
end
|
301
|
+
|
302
|
+
# Pre-0.8.0 user code may have passed *nil* as the second parameter,
|
303
|
+
# in order to use the @response_group of the operation.
|
304
|
+
#
|
305
|
+
nr_pages ||= 1
|
306
|
+
|
307
|
+
# Check to see whether a particular version of the API has been
|
308
|
+
# requested. If so, overwrite Version with the new value.
|
309
|
+
#
|
310
|
+
parameters.merge!( { 'Version' => @api } ) if @api
|
311
|
+
|
312
|
+
@query = Amazon::AWS.assemble_query( parameters, @encoding )
|
313
|
+
page = Amazon::AWS.get_page( self )
|
314
|
+
|
315
|
+
# Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
|
316
|
+
#
|
317
|
+
page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
|
318
|
+
|
319
|
+
doc = Document.new( page )
|
320
|
+
|
321
|
+
# Some errors occur at the very top level of the XML. For example,
|
322
|
+
# when no Operation parameter is given. This should not be possible
|
323
|
+
# with user code, but occurred during debugging of this library.
|
324
|
+
#
|
325
|
+
error_check( doc )
|
326
|
+
|
327
|
+
# Another possible error results in a document containing nothing
|
328
|
+
# but <Result>Internal Error</Result>. This occurs when a specific
|
329
|
+
# version of the AWS API is requested, in combination with an
|
330
|
+
# operation that did not yet exist in that version of the API.
|
331
|
+
#
|
332
|
+
# For example:
|
333
|
+
#
|
334
|
+
# http://ecs.amazonaws.com/onca/xml?AWSAccessKeyId=foo&Operation=VehicleSearch&Year=2008&ResponseGroup=VehicleMakes&Service=AWSECommerceService&Version=2008-03-03
|
335
|
+
#
|
336
|
+
if xml = doc.elements['Result']
|
337
|
+
raise Amazon::AWS::Error::AWSError, xml.text
|
338
|
+
end
|
339
|
+
|
340
|
+
# Fundamental errors happen at the OperationRequest level. For
|
341
|
+
# example, if an invalid AWSAccessKeyId is used.
|
342
|
+
#
|
343
|
+
error_check( doc.elements['*/OperationRequest'] )
|
344
|
+
|
345
|
+
# Check for parameter and value errors deeper down, inside Request.
|
346
|
+
#
|
347
|
+
if operation.kind == 'MultipleOperation'
|
348
|
+
|
349
|
+
# Everything is a level deeper, because of the
|
350
|
+
# <MultiOperationResponse> container.
|
351
|
+
#
|
352
|
+
# Check for errors in the first operation.
|
353
|
+
#
|
354
|
+
error_check( doc.elements['*/*/*/Request'] )
|
355
|
+
|
356
|
+
# Check for errors in the second operation.
|
357
|
+
#
|
358
|
+
error_check( doc.elements['*/*[3]/*/Request'] )
|
359
|
+
|
360
|
+
# If second operation is batched, check for errors in its 2nd set
|
361
|
+
# of results.
|
362
|
+
#
|
363
|
+
if batched = doc.elements['*/*[3]/*[2]/Request']
|
364
|
+
error_check( batched )
|
365
|
+
end
|
366
|
+
else
|
367
|
+
error_check( doc.elements['*/*/Request'] )
|
368
|
+
|
369
|
+
# If operation is batched, check for errors in its 2nd set of
|
370
|
+
# results.
|
371
|
+
#
|
372
|
+
if batched = doc.elements['*/*[3]/Request']
|
373
|
+
error_check( batched )
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
if doc.elements['*/*[2]/TotalPages']
|
378
|
+
total_pages = doc.elements['*/*[2]/TotalPages'].text.to_i
|
379
|
+
|
380
|
+
# FIXME: ListLookup and MultipleOperation (and possibly others) have
|
381
|
+
# TotalPages nested one level deeper. I should take some time to
|
382
|
+
# ensure that all operations that can return multiple results pages
|
383
|
+
# are covered by either the 'if' above or the 'elsif' here.
|
384
|
+
#
|
385
|
+
elsif doc.elements['*/*[2]/*[2]/TotalPages']
|
386
|
+
total_pages = doc.elements['*/*[2]/*[2]/TotalPages'].text.to_i
|
387
|
+
else
|
388
|
+
total_pages = 1
|
389
|
+
end
|
390
|
+
|
391
|
+
# Create a root AWS object and walk the XML response tree.
|
392
|
+
#
|
393
|
+
aws = AWS::AWSObject.new( operation )
|
394
|
+
aws.walk( doc )
|
395
|
+
result = aws
|
396
|
+
|
397
|
+
# If only one page has been requested or only one page is available,
|
398
|
+
# we can stop here. First yield to the block, if given.
|
399
|
+
#
|
400
|
+
if nr_pages == 1 || ( tp = total_pages ) == 1
|
401
|
+
yield result if block_given?
|
402
|
+
return result
|
403
|
+
end
|
404
|
+
|
405
|
+
# Limit the number of pages to the maximum number available.
|
406
|
+
#
|
407
|
+
nr_pages = tp.to_i if nr_pages == :ALL_PAGES || nr_pages > tp.to_i
|
408
|
+
|
409
|
+
if PAGINATION.key? operation.kind
|
410
|
+
page_parameter = PAGINATION[operation.kind]['parameter']
|
411
|
+
max_pages = PAGINATION[operation.kind]['max_page']
|
412
|
+
else
|
413
|
+
page_parameter = 'ItemPage'
|
414
|
+
max_pages = 400
|
415
|
+
end
|
416
|
+
|
417
|
+
# Iterate over pages 2 and higher, but go no higher than MAX_PAGES.
|
418
|
+
#
|
419
|
+
2.upto( nr_pages < max_pages ? nr_pages : max_pages ) do |page_nr|
|
420
|
+
@query = Amazon::AWS.assemble_query(
|
421
|
+
parameters.merge( { page_parameter => page_nr } ),
|
422
|
+
@encoding)
|
423
|
+
page = Amazon::AWS.get_page( self )
|
424
|
+
|
425
|
+
# Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
|
426
|
+
#
|
427
|
+
page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
|
428
|
+
|
429
|
+
doc = Document.new( page )
|
430
|
+
|
431
|
+
# Check for errors.
|
432
|
+
#
|
433
|
+
error_check( doc.elements['*/OperationRequest'] )
|
434
|
+
error_check( doc.elements['*/*/Request'] )
|
435
|
+
|
436
|
+
# Create a new AWS object and walk the XML response tree.
|
437
|
+
#
|
438
|
+
aws = AWS::AWSObject.new( operation )
|
439
|
+
aws.walk( doc )
|
440
|
+
|
441
|
+
# When dealing with multiple pages, we return not just an
|
442
|
+
# AWSObject, but an array of them.
|
443
|
+
#
|
444
|
+
result = [ result ] unless result.is_a? Array
|
445
|
+
|
446
|
+
# Append the new object to the array.
|
447
|
+
#
|
448
|
+
result << aws
|
449
|
+
end
|
450
|
+
|
451
|
+
# Yield each object to the block, if given.
|
452
|
+
#
|
453
|
+
result.each { |r| yield r } if block_given?
|
454
|
+
|
455
|
+
result
|
456
|
+
end
|
457
|
+
|
458
|
+
end
|
459
|
+
|
460
|
+
end
|
461
|
+
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|