ruby-aaws 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # $Id: similarity_lookup1,v 1.1 2008/04/27 06:03:54 ianmacd Exp $
4
+
5
+ require 'amazon/aws'
6
+ require 'amazon/aws/search'
7
+
8
+ include Amazon::AWS
9
+ include Amazon::AWS::Search
10
+
11
+ # Example of a batch operation, using the ASIN as the shared ID.
12
+ #
13
+ sl = SimilarityLookup.new( [ 'B000AE4QEC', 'B000051WBE' ] )
14
+
15
+ # You can have multiple response groups.
16
+ #
17
+ rg = ResponseGroup.new( 'Medium', 'Offers', 'Reviews' )
18
+
19
+ req = Request.new
20
+ req.locale = 'uk'
21
+
22
+ resp = req.search( sl, rg )
23
+ item_sets = resp.similarity_lookup_response[0].items
24
+
25
+ item_sets.each do |item_set|
26
+ item_set.item.each do |item|
27
+ attribs = item.item_attributes[0]
28
+ puts attribs.label
29
+ if attribs.list_price
30
+ puts attribs.title, attribs.list_price[0].formatted_price
31
+ end
32
+
33
+ # Availability has become a cumbersome thing to retrieve in AWSv4.
34
+ #
35
+ puts 'Availability: %s' %
36
+ [ item.offers[0].offer[0].offer_listing[0].availability ]
37
+ puts 'Average rating: %s' % [ item.customer_reviews[0].average_rating ]
38
+ puts 'Reviewed by %s customers.' %
39
+ [ item.customer_reviews[0].total_reviews ]
40
+
41
+ puts 'Customers said:'
42
+ item.customer_reviews[0].review.each do |review|
43
+ puts ' %s (%s votes)' % [ review.summary, review.total_votes ]
44
+ end
45
+
46
+ puts
47
+ end
48
+ end
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # $Id: tag_lookup1,v 1.1 2008/04/27 19:46:43 ianmacd Exp $
4
+
5
+ require 'amazon/aws'
6
+ require 'amazon/aws/search'
7
+
8
+ include Amazon::AWS
9
+ include Amazon::AWS::Search
10
+
11
+ tag_str = 'Awful'
12
+ tl = TagLookup.new( tag_str )
13
+
14
+ # You can have multiple response groups.
15
+ #
16
+ rg = ResponseGroup.new( 'Tags', 'TagsSummary' )
17
+
18
+ req = Request.new
19
+ req.locale = 'us'
20
+
21
+ resp = req.search( tl, rg )
22
+ tag = resp.tag_lookup_response.tags.tag
23
+
24
+ printf( "Tag name '%s' has %d distinct items.\n", tag_str, tag.distinct_items )
25
+ printf( "Tag has %d distinct items.\n", tag.distinct_users )
26
+ printf( "Tag has %d total usages.\n", tag.total_usages )
27
+ printf( "Tagged for the first time in entity %s on %s\nby %s..\n",
28
+ tag.first_tagging.entity_id,
29
+ tag.first_tagging.time,
30
+ tag.first_tagging.user_id )
31
+ printf( "Tagged for the last time in entity %s on %s\nby %s..\n",
32
+ tag.last_tagging.entity_id,
33
+ tag.last_tagging.time,
34
+ tag.last_tagging.user_id )
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # $Id: transaction_lookup1,v 1.1 2008/04/27 21:24:21 ianmacd Exp $
4
+
5
+ require 'amazon/aws'
6
+ require 'amazon/aws/search'
7
+
8
+ include Amazon::AWS
9
+ include Amazon::AWS::Search
10
+
11
+ tl = TransactionLookup.new( '103-5663398-5028241' )
12
+
13
+ rg = ResponseGroup.new( 'TransactionDetails' )
14
+
15
+ req = Request.new
16
+ req.locale = 'us'
17
+
18
+ resp = req.search( tl, rg )
19
+ trans = resp.transaction_lookup_response.transactions.transaction
20
+
21
+ printf( "Transaction date was %s.\n", trans.transaction_date )
22
+ printf( "It was in the amount of %s and the seller was %s.\n",
23
+ trans.totals.total.formatted_price, trans.seller_name )
24
+ printf( "The shipping charge was %s and the package was sent by %s.\n",
25
+ trans.totals.shipping_charge.formatted_price,
26
+ trans.shipments.shipment.delivery_method )
@@ -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,317 @@
1
+ # $Id: search.rb,v 1.21 2008/05/31 19:42:04 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
+
12
+ # Load this library with:
13
+ #
14
+ # require 'amazon/aws/search'
15
+ #
16
+ module Search
17
+
18
+ class Request
19
+
20
+ include REXML
21
+
22
+ # Exception class for bad access key ID.
23
+ #
24
+ class AccessKeyIdError < StandardError; end
25
+
26
+ # Exception class for bad locales.
27
+ #
28
+ class LocaleError < StandardError; end
29
+
30
+ attr_reader :conn, :locale, :user_agent
31
+ attr_writer :cache
32
+
33
+ # This method is used to generate an AWS search request object.
34
+ #
35
+ # _key_id_ is your AWS {access key
36
+ # ID}[https://aws-portal.amazon.com/gp/aws/developer/registration/index.html],
37
+ # _associate_ is your
38
+ # Associates[http://docs.amazonwebservices.com/AWSECommerceService/2008-04-07/GSG/BecominganAssociate.html]
39
+ # tag (if any), _locale_ is the locale in which you which to work
40
+ # (*us* for amazon.com[http://www.amazon.com/], *uk* for
41
+ # amazon.co.uk[http://www.amazon.co.uk], etc.), _cache_ is whether or
42
+ # not you wish to utilise a response cache, and _user_agent_ is the
43
+ # client name to pass when performing calls to AWS. By default,
44
+ # _user_agent_ will be set to a string identifying the Ruby/AWS
45
+ # library and its version number.
46
+ #
47
+ # _locale_ and _cache_ can also be set later, if you wish to change
48
+ # the current behaviour.
49
+ #
50
+ # Example:
51
+ #
52
+ # req = Request.new( '0Y44V8FAFNM119CX4TR2', 'calibanorg-20' )
53
+ #
54
+ def initialize(key_id=nil, associate=nil, locale=nil, cache=nil,
55
+ user_agent=USER_AGENT)
56
+
57
+ @config ||= Amazon::Config.new
58
+
59
+ def_locale = locale
60
+ locale = 'us' unless locale
61
+ locale.downcase!
62
+
63
+ key_id ||= @config['key_id']
64
+ cache = @config['cache'] if cache.nil?
65
+
66
+ # Take locale from config file if no locale was passed to method.
67
+ #
68
+ if @config.key?( 'locale' ) && ! def_locale
69
+ locale = @config['locale']
70
+ end
71
+ validate_locale( locale )
72
+
73
+ if key_id.nil?
74
+ raise AccessKeyIdError, 'key_id may not be nil'
75
+ end
76
+
77
+ @key_id = key_id
78
+ @tag = associate || @config['associate'] || DEF_ASSOC[locale]
79
+ @user_agent = user_agent
80
+ @cache = unless cache == 'false' || cache == false
81
+ Amazon::AWS::Cache.new( @config['cache_dir'] )
82
+ else
83
+ nil
84
+ end
85
+ self.locale = locale
86
+ end
87
+
88
+
89
+ # Assign a new locale. If the locale we're coming from is using the
90
+ # default Associate ID for that locale, then we use the new locale's
91
+ # default ID, too.
92
+ #
93
+ def locale=(l) # :nodoc:
94
+ old_locale = @locale ||= nil
95
+ @locale = validate_locale( l )
96
+
97
+ # Use the new locale's default ID if the ID currently in use is the
98
+ # current locale's default ID.
99
+ #
100
+ if @tag == Amazon::AWS::DEF_ASSOC[old_locale]
101
+ @tag = Amazon::AWS::DEF_ASSOC[@locale]
102
+ end
103
+
104
+ # We must now set up a new HTTP connection to the correct server for
105
+ # this locale, unless the same server is used for both.
106
+ #
107
+ unless Amazon::AWS::ENDPOINT[@locale] ==
108
+ Amazon::AWS::ENDPOINT[old_locale]
109
+ #connect( @locale )
110
+ @conn = nil
111
+ end
112
+ end
113
+
114
+
115
+ # If @cache has simply been assigned *true* at some point in time,
116
+ # assign a proper cache object to it when it is referenced. Otherwise,
117
+ # just return its value.
118
+ #
119
+ def cache # :nodoc:
120
+ if @cache == true
121
+ @cache = Amazon::AWS::Cache.new( @config['cache_dir'] )
122
+ else
123
+ @cache
124
+ end
125
+ end
126
+
127
+
128
+ # Verify the validity of a locale string. _l_ is the locale string.
129
+ #
130
+ def validate_locale(l)
131
+ unless Amazon::AWS::ENDPOINT.has_key? l
132
+ raise LocaleError, "invalid locale: #{l}"
133
+ end
134
+ l
135
+ end
136
+ private :validate_locale
137
+
138
+
139
+ # Return an HTTP connection for the current _locale_.
140
+ #
141
+ def connect(locale)
142
+ if ENV.key? 'http_proxy'
143
+ uri = URI.parse( ENV['http_proxy'] )
144
+ proxy_user = proxy_pass = nil
145
+ proxy_user, proxy_pass = uri.userinfo.split( /:/ ) if uri.userinfo
146
+ @conn = Net::HTTP::Proxy( uri.host, uri.port, proxy_user,
147
+ proxy_pass ).start(
148
+ Amazon::AWS::ENDPOINT[locale].host )
149
+ else
150
+ @conn = Net::HTTP::start( Amazon::AWS::ENDPOINT[locale].host )
151
+ end
152
+ end
153
+ private :connect
154
+
155
+
156
+ # Reconnect to the server if our connection has been lost (due to a
157
+ # time-out, etc.).
158
+ #
159
+ def reconnect # :nodoc:
160
+ connect( self.locale )
161
+ self
162
+ end
163
+
164
+
165
+ # This method checks for errors in an XML response returned by AWS.
166
+ # _xml_ is the XML node below which to search.
167
+ #
168
+ def error_check(xml)
169
+ if xml = xml.elements['Errors/Error']
170
+ error = Amazon::AWS::Error::AWSError.new( xml )
171
+ raise error.exception
172
+ end
173
+ end
174
+ private :error_check
175
+
176
+
177
+ # Perform a search of the AWS catalogue. _operation_ is one of the
178
+ # objects subclased from _Operation_, such as _ItemSearch_,
179
+ # _ItemLookup_, etc. It may also be a _MultipleOperation_ object.
180
+ #
181
+ # _response_group_ will apply to all both operations contained in
182
+ # _operation_, if _operation_ is a _MultipleOperation_ object.
183
+ #
184
+ # _nr_pages_ is the number of results pages to return. It defaults to
185
+ # <b>1</b>. If a higher number is given, pages 1 to _nr_pages_ will be
186
+ # returned. If the special value <b>:ALL_PAGES</b> is given, all
187
+ # results pages will be returned.
188
+ #
189
+ # If operation is of class _MultipleOperation_, the operations
190
+ # combined within will return only the first page, regardless of
191
+ # whether a higher number of pages is requested.
192
+ #
193
+ def search(operation, response_group, nr_pages=1)
194
+ q_params = Amazon::AWS::SERVICE.
195
+ merge( { 'AWSAccessKeyId' => @key_id,
196
+ 'AssociateTag' => @tag } ).
197
+ merge( operation.params ).
198
+ merge( response_group.params )
199
+
200
+ query = Amazon::AWS.assemble_query( q_params )
201
+ page = Amazon::AWS.get_page( self, query )
202
+ doc = Document.new( page )
203
+
204
+ # Some errors occur at the very top level of the XML. For example,
205
+ # when no Operation parameter is given. This should not be possible
206
+ # with user code, but occurred during debugging of this library.
207
+ #
208
+ error_check( doc )
209
+
210
+ # Fundamental errors happen at the OperationRequest level. For
211
+ # example, if an invalid AWSAccessKeyId is used.
212
+ #
213
+ error_check( doc.elements['*/OperationRequest'] )
214
+
215
+ # Check for parameter and value errors deeper down, inside Request.
216
+ #
217
+ if operation.kind == 'MultipleOperation'
218
+
219
+ # Everything is a level deeper, because of the
220
+ # <MultiOperationResponse> container.
221
+ #
222
+ # Check for errors in the first operation.
223
+ #
224
+ error_check( doc.elements['*/*/*/Request'] )
225
+
226
+ # Check for errors in the second operation.
227
+ #
228
+ error_check( doc.elements['*/*[3]/*/Request'] )
229
+
230
+ # If second operation is batched, check for errors in its 2nd set
231
+ # of results.
232
+ #
233
+ if batched = doc.elements['*/*[3]/*[2]/Request']
234
+ error_check( batched )
235
+ end
236
+ else
237
+ error_check( doc.elements['*/*/Request'] )
238
+
239
+ # If operation is batched, check for errors in its 2nd set of
240
+ # results.
241
+ #
242
+ if batched = doc.elements['*/*[3]/Request']
243
+ error_check( batched )
244
+ end
245
+ end
246
+
247
+ # FIXME: This doesn't work if a MultipleOperation was used, because
248
+ # <TotalPages> will be nested one level deeper. It's therefore
249
+ # currently only possible to return the first page of results
250
+ # for operations combined in a MultipleOperation.
251
+ #
252
+ if doc.elements['*/*[2]/TotalPages']
253
+ total_pages = doc.elements['*/*[2]/TotalPages'].text.to_i
254
+ else
255
+ total_pages = 1
256
+ end
257
+
258
+ # Create a root AWS object and walk the XML response tree.
259
+ #
260
+ aws = AWS::AWSObject.new( operation )
261
+ aws.walk( doc )
262
+ result = aws
263
+
264
+ # If only one page has been requested or only one page is available,
265
+ # we can stop here. First yield to the block, if given.
266
+ #
267
+ if nr_pages == 1 || ( tp = total_pages ) == 1
268
+ yield result if block_given?
269
+ return result
270
+ end
271
+
272
+ # Limit the number of pages to the maximum number available.
273
+ #
274
+ nr_pages = tp.to_i if nr_pages == :ALL_PAGES || nr_pages > tp.to_i
275
+
276
+ # Iterate over pages 2 and higher, but go no higher than MAX_PAGES.
277
+ #
278
+ 2.upto( nr_pages < MAX_PAGES ? nr_pages : MAX_PAGES ) do |page_nr|
279
+ query = Amazon::AWS.assemble_query(
280
+ q_params.merge( { 'ItemPage' => page_nr } ) )
281
+ page = Amazon::AWS.get_page( self, query )
282
+ doc = Document.new( page )
283
+
284
+ # Check for errors.
285
+ #
286
+ error_check( doc.elements['*/OperationRequest'] )
287
+ error_check( doc.elements['*/*/Request'] )
288
+
289
+ # Create a new AWS object and walk the XML response tree.
290
+ #
291
+ aws = AWS::AWSObject.new
292
+ aws.walk( doc )
293
+
294
+ # When dealing with multiple pages, we return not just an
295
+ # AWSObject, but an array of them.
296
+ #
297
+ result = [ result ] unless result.is_a? Array
298
+
299
+ # Append the new object to the array.
300
+ #
301
+ result << aws
302
+ end
303
+
304
+ # Yield each object to the block, if given.
305
+ #
306
+ result.each { |r| yield r } if block_given?
307
+
308
+ result
309
+ end
310
+
311
+ end
312
+
313
+ end
314
+
315
+ end
316
+
317
+ end