ruby-aaws 0.4.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.
@@ -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