kernow-ruby-aaws 0.5.4 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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