kernow-ruby-aaws 0.5.4 → 0.7.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.
@@ -1,4 +1,4 @@
1
- # $Id: search.rb,v 1.26 2008/09/21 22:17:32 ianmacd Exp $
1
+ # $Id: search.rb,v 1.43 2009/06/14 22:28:27 ianmacd Exp $
2
2
  #
3
3
 
4
4
  module Amazon
@@ -8,6 +8,7 @@ module Amazon
8
8
  require 'amazon/aws'
9
9
  require 'net/http'
10
10
  require 'rexml/document'
11
+ require 'openssl'
11
12
 
12
13
  # Load this library with:
13
14
  #
@@ -27,15 +28,31 @@ module Amazon
27
28
  #
28
29
  class LocaleError < Amazon::AWS::Error::AWSError; end
29
30
 
30
- attr_reader :conn, :locale, :user_agent
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
31
44
  attr_writer :cache
45
+ attr_accessor :encoding, :cache_dir
32
46
 
33
47
  # This method is used to generate an AWS search request object.
34
48
  #
35
49
  # _key_id_ is your AWS {access key
36
- # ID}[https://aws-portal.amazon.com/gp/aws/developer/registration/index.html],
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
+ #
37
54
  # _associate_ is your
38
- # Associates[http://docs.amazonwebservices.com/AWSECommerceService/2008-04-07/GSG/BecominganAssociate.html]
55
+ # Associates[http://docs.amazonwebservices.com/AWSECommerceService/2009-03-31/GSG/BecominganAssociate.html]
39
56
  # tag (if any), _locale_ is the locale in which you which to work
40
57
  # (*us* for amazon.com[http://www.amazon.com/], *uk* for
41
58
  # amazon.co.uk[http://www.amazon.co.uk], etc.), _cache_ is whether or
@@ -62,7 +79,7 @@ module Amazon
62
79
 
63
80
  key_id ||= @config['key_id']
64
81
  cache = @config['cache'] if cache.nil?
65
- cache_dir ||= @config['cache_dir']
82
+ @cache_dir = cache_dir.nil? ? @config['cache_dir'] : cache_dir
66
83
 
67
84
  # Take locale from config file if no locale was passed to method.
68
85
  #
@@ -79,10 +96,17 @@ module Amazon
79
96
  @tag = associate || @config['associate'] || DEF_ASSOC[locale]
80
97
  @user_agent = user_agent
81
98
  @cache = unless cache == 'false' || cache == false
82
- Amazon::AWS::Cache.new( cache_dir )
99
+ Amazon::AWS::Cache.new( @cache_dir )
83
100
  else
84
101
  nil
85
102
  end
103
+
104
+ # Set the following two variables from the config file. Will be
105
+ # *nil* if not present in config file.
106
+ #
107
+ @api = @config['api']
108
+ @encoding = @config['encoding']
109
+
86
110
  self.locale = locale
87
111
  end
88
112
 
@@ -102,6 +126,10 @@ module Amazon
102
126
  @tag = Amazon::AWS::DEF_ASSOC[@locale]
103
127
  end
104
128
 
129
+ if @config.key?( @locale ) && @config[@locale].key?( 'associate' )
130
+ @tag = @config[@locale]['associate']
131
+ end
132
+
105
133
  # We must now set up a new HTTP connection to the correct server for
106
134
  # this locale, unless the same server is used for both.
107
135
  #
@@ -119,7 +147,7 @@ module Amazon
119
147
  #
120
148
  def cache # :nodoc:
121
149
  if @cache == true
122
- @cache = Amazon::AWS::Cache.new( @config['cache_dir'] )
150
+ @cache = Amazon::AWS::Cache.new( @cache_dir )
123
151
  else
124
152
  @cache
125
153
  end
@@ -167,19 +195,76 @@ module Amazon
167
195
  # _xml_ is the XML node below which to search.
168
196
  #
169
197
  def error_check(xml)
170
- if xml = xml.elements['Errors/Error']
198
+ if ! xml.nil? && xml = xml.elements['Errors/Error']
171
199
  raise Amazon::AWS::Error.exception( xml )
172
200
  end
173
201
  end
174
202
  private :error_check
175
203
 
176
204
 
177
- # Perform a search of the AWS database. _operation_ is one of the
178
- # objects subclassed from _Operation_, such as _ItemSearch_,
179
- # _ItemLookup_, etc. It may also be a _MultipleOperation_ object.
205
+ # Add a timestamp to a request object's query string.
180
206
  #
181
- # _response_group_ will apply to all both operations contained in
182
- # _operation_, if _operation_ is a _MultipleOperation_ object.
207
+ def timestamp
208
+ @query << '&Timestamp=%s' %
209
+ [ Amazon.url_encode(
210
+ Time.now.utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) ) ]
211
+ end
212
+ private :timestamp
213
+
214
+
215
+ # Add a signature to a request object's query string. This implicitly
216
+ # also adds a timestamp.
217
+ #
218
+ def sign
219
+ return false unless DIGEST_SUPPORT
220
+
221
+ timestamp
222
+ params = @query[1..-1].split( '&' ).sort.join( '&' )
223
+
224
+ sign_str = "GET\n%s\n%s\n%s" % [ ENDPOINT[@locale].host,
225
+ ENDPOINT[@locale].path,
226
+ params ]
227
+
228
+ Amazon.dprintf( 'Calculating SHA256 HMAC of "%s"...', sign_str )
229
+
230
+ hmac = OpenSSL::HMAC.digest( DIGEST,
231
+ @config['secret_key_id'],
232
+ sign_str )
233
+ Amazon.dprintf( 'SHA256 HMAC is "%s"', hmac.inspect )
234
+
235
+ base64_hmac = [ hmac ].pack( 'm' ).chomp
236
+ Amazon.dprintf( 'Base64-encoded HMAC is "%s".', base64_hmac )
237
+
238
+ signature = Amazon.url_encode( base64_hmac )
239
+
240
+ params << '&Signature=%s' % [ signature ]
241
+ @query = '?' + params
242
+
243
+ true
244
+ end
245
+
246
+
247
+ # Perform a search of the AWS database, returning an AWSObject.
248
+ #
249
+ # _operation_ is an object of a subclass of _Operation_, such as
250
+ # _ItemSearch_, _ItemLookup_, etc. It may also be a _MultipleOperation_
251
+ # object.
252
+ #
253
+ # _response_group_, if supplied, is a set of one or more response
254
+ # groups to use in combination with _operation_ for the purpose of
255
+ # determining which data sets AWS should return.
256
+ #
257
+ # If _response_group_ is *nil*, Ruby/AWS will instead use the response
258
+ # groups specified by the _@response_group_ attribute of _operation_.
259
+ # That is now the preferred way of specifying response groups to use
260
+ # with a given operation. The _response_group_ parameter may later be
261
+ # removed from this method altogether.
262
+ #
263
+ # If _response_group_ is given, it will apply to all sub-operations of
264
+ # _operation_, if _operation_ is of class MultipleOperation. To use a
265
+ # different set of response groups for each sub-operation, you should
266
+ # assign to the _@response_group_ attribute of each of them before
267
+ # instantiating a MultipleOperation to combine them.
183
268
  #
184
269
  # _nr_pages_ is the number of results pages to return. It defaults to
185
270
  # <b>1</b>. If a higher number is given, pages 1 to _nr_pages_ will be
@@ -189,25 +274,41 @@ module Amazon
189
274
  # The maximum page number that can be returned for each type of
190
275
  # operation is documented in the AWS Developer's Guide:
191
276
  #
192
- # http://docs.amazonwebservices.com/AWSECommerceService/2008-08-19/DG/index.html?CHAP_MakingRequestsandUnderstandingResponses.html#PagingThroughResults
277
+ # http://docs.amazonwebservices.com/AWSECommerceService/2009-03-31/DG/index.html?MaximumNumberofPages.html
193
278
  #
194
279
  # Note that _ItemLookup_ operations can use three separate pagination
195
280
  # parameters. Ruby/AWS, however, uses _OfferPage_ for the purposes of
196
281
  # returning multiple pages.
197
282
  #
198
283
  # If operation is of class _MultipleOperation_, the operations
199
- # combined within will return only the first page, regardless of
284
+ # specified within will return only the first page, regardless of
200
285
  # whether a higher number of pages is requested.
201
286
  #
202
- def search(operation, response_group, nr_pages=1)
203
- q_params = Amazon::AWS::SERVICE.
204
- merge( { 'AWSAccessKeyId' => @key_id,
205
- 'AssociateTag' => @tag } ).
206
- merge( operation.params ).
207
- merge( response_group.params )
208
-
209
- query = Amazon::AWS.assemble_query( q_params )
210
- page = Amazon::AWS.get_page( self, query )
287
+ # If a block is passed to this method, each successive page of results
288
+ # will be yielded to the block.
289
+ #
290
+ def search(operation, response_group=nil, nr_pages=1)
291
+ response_group ||=
292
+ operation.response_group || ResponseGroup.new( :Large )
293
+
294
+ parameters = Amazon::AWS::SERVICE.
295
+ merge( { 'AWSAccessKeyId' => @key_id,
296
+ 'AssociateTag' => @tag } ).
297
+ merge( operation.params ).
298
+ merge( response_group.params )
299
+
300
+ # Check to see whether a particular version of the API has been
301
+ # requested. If so, overwrite Version with the new value.
302
+ #
303
+ parameters.merge!( { 'Version' => @api } ) if @api
304
+
305
+ @query = Amazon::AWS.assemble_query( parameters, @encoding )
306
+ page = Amazon::AWS.get_page( self )
307
+
308
+ # Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
309
+ #
310
+ page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
311
+
211
312
  doc = Document.new( page )
212
313
 
213
314
  # Some errors occur at the very top level of the XML. For example,
@@ -216,6 +317,19 @@ module Amazon
216
317
  #
217
318
  error_check( doc )
218
319
 
320
+ # Another possible error results in a document containing nothing
321
+ # but <Result>Internal Error</Result>. This occurs when a specific
322
+ # version of the AWS API is requested, in combination with an
323
+ # operation that did not yet exist in that version of the API.
324
+ #
325
+ # For example:
326
+ #
327
+ # http://ecs.amazonaws.com/onca/xml?AWSAccessKeyId=foo&Operation=VehicleSearch&Year=2008&ResponseGroup=VehicleMakes&Service=AWSECommerceService&Version=2008-03-03
328
+ #
329
+ if xml = doc.elements['Result']
330
+ raise Amazon::AWS::Error::AWSError, xml.text
331
+ end
332
+
219
333
  # Fundamental errors happen at the OperationRequest level. For
220
334
  # example, if an invalid AWSAccessKeyId is used.
221
335
  #
@@ -293,9 +407,15 @@ module Amazon
293
407
  # Iterate over pages 2 and higher, but go no higher than MAX_PAGES.
294
408
  #
295
409
  2.upto( nr_pages < max_pages ? nr_pages : max_pages ) do |page_nr|
296
- query = Amazon::AWS.assemble_query(
297
- q_params.merge( { page_parameter => page_nr } ) )
298
- page = Amazon::AWS.get_page( self, query )
410
+ @query = Amazon::AWS.assemble_query(
411
+ parameters.merge( { page_parameter => page_nr } ),
412
+ @encoding)
413
+ page = Amazon::AWS.get_page( self )
414
+
415
+ # Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
416
+ #
417
+ page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'
418
+
299
419
  doc = Document.new( page )
300
420
 
301
421
  # Check for errors.
@@ -305,7 +425,7 @@ module Amazon
305
425
 
306
426
  # Create a new AWS object and walk the XML response tree.
307
427
  #
308
- aws = AWS::AWSObject.new
428
+ aws = AWS::AWSObject.new( operation )
309
429
  aws.walk( doc )
310
430
 
311
431
  # When dealing with multiple pages, we return not just an
data/ruby-aaws.gemspec ADDED
@@ -0,0 +1,117 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ruby-aaws}
5
+ s.version = "0.7.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ian Macdonald", "Jamie Dyer"]
9
+ s.date = %q{2009-08-17}
10
+ s.description = %q{Ruby interface to Amazon Associates Web Services}
11
+ s.email = %q{ian@caliban.org}
12
+ s.extra_rdoc_files = [
13
+ "README",
14
+ "README.rdoc"
15
+ ]
16
+ s.files = [
17
+ "COPYING",
18
+ "INSTALL",
19
+ "NEWS",
20
+ "README",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "example/batch_operation",
25
+ "example/browse_node_lookup1",
26
+ "example/customer_content_lookup1",
27
+ "example/customer_content_search1",
28
+ "example/example1",
29
+ "example/help1",
30
+ "example/item_lookup1",
31
+ "example/item_lookup2",
32
+ "example/item_search1",
33
+ "example/item_search2",
34
+ "example/item_search3",
35
+ "example/list_lookup1",
36
+ "example/list_search1",
37
+ "example/multiple_operation1",
38
+ "example/seller_listing_lookup1",
39
+ "example/seller_listing_search1",
40
+ "example/seller_lookup1",
41
+ "example/shopping_cart1",
42
+ "example/similarity_lookup1",
43
+ "example/tag_lookup1",
44
+ "example/transaction_lookup1",
45
+ "example/vehicle_search",
46
+ "lib/amazon.rb",
47
+ "lib/amazon/aws.rb",
48
+ "lib/amazon/aws/cache.rb",
49
+ "lib/amazon/aws/search.rb",
50
+ "lib/amazon/aws/shoppingcart.rb",
51
+ "lib/amazon/locale.rb",
52
+ "ruby-aaws.gemspec",
53
+ "setup.rb",
54
+ "test/setup.rb",
55
+ "test/tc_amazon.rb",
56
+ "test/tc_aws.rb",
57
+ "test/tc_browse_node_lookup.rb",
58
+ "test/tc_customer_content_lookup.rb",
59
+ "test/tc_help.rb",
60
+ "test/tc_item_lookup.rb",
61
+ "test/tc_item_search.rb",
62
+ "test/tc_list_lookup.rb",
63
+ "test/tc_list_search.rb",
64
+ "test/tc_multiple_operation.rb",
65
+ "test/tc_operation_request.rb",
66
+ "test/tc_seller_listing_lookup.rb",
67
+ "test/tc_seller_listing_search.rb",
68
+ "test/tc_seller_lookup.rb",
69
+ "test/tc_serialisation.rb",
70
+ "test/tc_shopping_cart.rb",
71
+ "test/tc_similarity_lookup.rb",
72
+ "test/tc_tag_lookup.rb",
73
+ "test/tc_transaction_lookup.rb",
74
+ "test/tc_vehicle_operations.rb",
75
+ "test/ts_aws.rb"
76
+ ]
77
+ s.has_rdoc = true
78
+ s.homepage = %q{http://www.caliban.org/ruby/ruby-aws/}
79
+ s.rdoc_options = ["--charset=UTF-8"]
80
+ s.require_paths = ["lib"]
81
+ s.rubygems_version = %q{1.3.1}
82
+ s.summary = %q{Ruby interface to Amazon Associates Web Services}
83
+ s.test_files = [
84
+ "test/setup.rb",
85
+ "test/tc_amazon.rb",
86
+ "test/tc_aws.rb",
87
+ "test/tc_browse_node_lookup.rb",
88
+ "test/tc_customer_content_lookup.rb",
89
+ "test/tc_help.rb",
90
+ "test/tc_item_lookup.rb",
91
+ "test/tc_item_search.rb",
92
+ "test/tc_list_lookup.rb",
93
+ "test/tc_list_search.rb",
94
+ "test/tc_multiple_operation.rb",
95
+ "test/tc_operation_request.rb",
96
+ "test/tc_seller_listing_lookup.rb",
97
+ "test/tc_seller_listing_search.rb",
98
+ "test/tc_seller_lookup.rb",
99
+ "test/tc_serialisation.rb",
100
+ "test/tc_shopping_cart.rb",
101
+ "test/tc_similarity_lookup.rb",
102
+ "test/tc_tag_lookup.rb",
103
+ "test/tc_transaction_lookup.rb",
104
+ "test/tc_vehicle_operations.rb",
105
+ "test/ts_aws.rb"
106
+ ]
107
+
108
+ if s.respond_to? :specification_version then
109
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
110
+ s.specification_version = 2
111
+
112
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
113
+ else
114
+ end
115
+ else
116
+ end
117
+ end
data/test/setup.rb CHANGED
@@ -1,4 +1,4 @@
1
- # $Id: setup.rb,v 1.3 2008/06/22 11:50:23 ianmacd Exp $
1
+ # $Id: setup.rb,v 1.5 2009/06/14 00:28:48 ianmacd Exp $
2
2
  #
3
3
 
4
4
  # Attempt to load Ruby/AWS using RubyGems.
@@ -24,8 +24,11 @@ class AWSTest < Test::Unit::TestCase
24
24
  @req = Request.new
25
25
  @req.locale = 'uk'
26
26
  @req.cache = false
27
+ @req.encoding = 'utf-8'
27
28
  end
28
29
 
29
- undef_method :default_test
30
+ # The default_test method needs to be removed before Ruby 1.9.0.
31
+ #
32
+ undef_method :default_test if method_defined? :default_test
30
33
 
31
34
  end
data/test/tc_aws.rb CHANGED
@@ -1,4 +1,4 @@
1
- # $Id: tc_aws.rb,v 1.11 2008/10/02 21:33:58 ianmacd Exp $
1
+ # $Id: tc_aws.rb,v 1.12 2009/06/14 00:29:28 ianmacd Exp $
2
2
  #
3
3
 
4
4
  require 'test/unit'
@@ -13,7 +13,7 @@ class TestAWSBasics < AWSTest
13
13
  CACHE_PATH = File.join( Dir.tmpdir, 'aws_cache' )
14
14
 
15
15
  def test_version
16
- v = '1.8.6'
16
+ v = '1.8.7'
17
17
  assert( RUBY_VERSION >= v, "Ruby version is lower than #{v}." )
18
18
  end
19
19
 
@@ -0,0 +1,62 @@
1
+ # $Id: tc_browse_node_lookup.rb,v 1.2 2009/06/02 00:39:43 ianmacd Exp $
2
+ #
3
+
4
+ require 'test/unit'
5
+ require './setup'
6
+
7
+ class TestBrowseNodeLookup < AWSTest
8
+
9
+ def test_browse_node_lookup
10
+
11
+ bnl = BrowseNodeLookup.new( 694212 )
12
+ rg = ResponseGroup.new( :BrowseNodeInfo )
13
+
14
+ response = @req.search( bnl, rg )
15
+
16
+ results = response.kernel
17
+
18
+ # Ensure we got some actual results back.
19
+ #
20
+ assert( results.size > 0 )
21
+
22
+ end
23
+
24
+ def test_browse_node_lookup_no_response_group
25
+
26
+ bnl = BrowseNodeLookup.new( 694212 )
27
+ bnl.response_group = ResponseGroup.new( :BrowseNodeInfo )
28
+ response = @req.search( bnl, nil )
29
+
30
+ results = response.kernel
31
+
32
+ # Ensure we got more than 10 results back.
33
+ #
34
+ assert( results.size > 0 )
35
+
36
+ end
37
+
38
+ def test_browse_node_lookup_class_method
39
+
40
+ response = Amazon::AWS.browse_node_lookup( 694212 )
41
+
42
+ results = response.kernel
43
+
44
+ # Ensure we got some actual results back.
45
+ #
46
+ assert( results.size > 0 )
47
+
48
+ end
49
+
50
+ def test_browse_node_lookup_class_method_block
51
+
52
+ Amazon::AWS.browse_node_lookup( '694212' ) do |r|
53
+
54
+ results = r.kernel
55
+
56
+ # Ensure we got some actual results back.
57
+ #
58
+ assert( results.size > 0 )
59
+ end
60
+ end
61
+
62
+ end