amazon_associate 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ at.clear_mappings
3
+
4
+ at.add_mapping(%r{^test/.*_test\.rb$}) {|f, _| [f] }
5
+ at.add_mapping(%r{^lib/amazon/(.*)\.rb$}) {|_, m| ["test/#{m[1]}_test.rb"] }
6
+ at.add_mapping(%r{^test/(test_helper)\.rb$}) { at.files_matching %r{^test/.*_test\.rb$} }
7
+ at.add_mapping(%r{^lib/.*\.rb$}) { at.files_matching %r{^test/.*_test\.rb$} }
8
+ end
@@ -0,0 +1,2 @@
1
+ *.swp
2
+ tags
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>amazon-ecs</name>
4
+ <comment></comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ <buildCommand>
9
+ <name>org.rubypeople.rdt.core.rubybuilder</name>
10
+ <arguments>
11
+ </arguments>
12
+ </buildCommand>
13
+ <buildCommand>
14
+ <name>com.ibm.etools.validation.validationbuilder</name>
15
+ <arguments>
16
+ </arguments>
17
+ </buildCommand>
18
+ </buildSpec>
19
+ <natures>
20
+ <nature>org.radrails.rails.ui.railsnature</nature>
21
+ <nature>org.rubypeople.rdt.core.rubynature</nature>
22
+ </natures>
23
+ </projectDescription>
@@ -0,0 +1,34 @@
1
+ 0.6.1 2008-11-10
2
+ * renamed to amazon_associate
3
+
4
+ 0.6 2008-11-09
5
+ * separated the classes in ecs.rb
6
+ * filesystem caching available
7
+ * more test coverage
8
+
9
+ 0.5.4 2008-10-21
10
+ * include Chris Martin's patches
11
+ * rename Gem to match Amazon's new name for ECS - Amazon Associates API
12
+ * update to git gemspec format
13
+
14
+ 0.5.3 2007-09-12
15
+ ----------------
16
+ * send_request to use default options.
17
+
18
+ 0.5.2 2007-09-08
19
+ ----------------
20
+ * Fixed AmazonAssociate::Element.get_unescaped error when result returned for given element path is nil
21
+
22
+ 0.5.1 2007-02-08
23
+ ----------------
24
+ * Fixed Amazon Japan and France URL error
25
+ * Removed opts.delete(:search_index) from item_lookup, SearchIndex param is allowed
26
+ when looking for a book with IdType other than the ASIN.
27
+ * Check for defined? RAILS_DEFAULT_LOGGER to avoid exception for non-rails ruby app
28
+ * Added check for LOGGER constant if RAILS_DEFAULT_LOGGER is not defined
29
+ * Added Ecs.configure(&proc) method for easier configuration of default options
30
+ * Added Element#search_and_convert method
31
+
32
+ 0.5.0 2006-09-12
33
+ ----------------
34
+ Initial Release
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2008 Dan Pickett, Enlight Solutions
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,120 @@
1
+ == amazon-associate
2
+
3
+ Generic Amazon E-commerce REST API using Hpricot with configurable
4
+ default options and method call options. Uses Response and
5
+ Element wrapper classes for easy access to REST XML output. It supports ECS 4.0.
6
+
7
+ It is generic, so you can easily extend <tt>AmazonAssociate::Request</tt> to support
8
+ other not implemented REST operations; and it is also generic because it just wraps around
9
+ Hpricot element object, instead of providing one-to-one object/attributes to XML elements map.
10
+
11
+ If in the future, there is a change in REST XML output structure,
12
+ no changes will be required on <tt>amazon-ecs</tt> library,
13
+ instead you just need to change the element path.
14
+
15
+ NOTE: You must now specify a secret key to support request signing as
16
+ required by Amazon.
17
+
18
+ Version: 0.6.1
19
+
20
+ == WANTS
21
+ * instance based refactoring (singletons are not ideal here)
22
+
23
+ == INSTALLATION
24
+
25
+ $ gem install dpickett-amazon_associate
26
+
27
+ == EXAMPLE
28
+
29
+ require 'amazon_associate'
30
+
31
+ # set the default options; options will be camelized and converted to REST request parameters.
32
+ AmazonAssociate::Request.configure do |options|
33
+ options[:aWS_access_key_id] = [your developer token]
34
+ options[:secrety_key] = [your secret key]
35
+ end
36
+
37
+ # options provided on method call will merge with the default options
38
+ res = AmazonAssociate::Request.item_search('ruby', {:response_group => 'Medium', :sort => 'salesrank'})
39
+
40
+ # some common response object methods
41
+ res.is_valid_request? # return true if request is valid
42
+ res.has_error? # return true if there is an error
43
+ res.error # return error message if there is any
44
+ res.total_pages # return total pages
45
+ res.total_results # return total results
46
+ res.item_page # return current page no if :item_page option is provided
47
+
48
+ # traverse through each item (AmazonAssociate::Element)
49
+ res.items.each do |item|
50
+ # retrieve string value using XML path
51
+ item.get('asin')
52
+ item.get('itemattributes/title')
53
+
54
+ # or return AmazonAssociate::Element instance
55
+ atts = item.search_and_convert('itemattributes')
56
+ atts.get('title')
57
+
58
+ # return first author or a string array of authors
59
+ atts.get('author') # 'Author 1'
60
+ atts.get_array('author') # ['Author 1', 'Author 2', ...]
61
+
62
+ # return an hash of children text values with the element names as the keys
63
+ item.get_hash('smallimage') # {:url => ..., :width => ..., :height => ...}
64
+
65
+ # note that '/' returns Hpricot::Elements array object, nil if not found
66
+ reviews = item/'editorialreview'
67
+
68
+ # traverse through Hpricot elements
69
+ reviews.each do |review|
70
+ # Getting hash value out of Hpricot element
71
+ AmazonAssociate::Element.get_hash(review) # [:source => ..., :content ==> ...]
72
+
73
+ # Or to get unescaped HTML values
74
+ AmazonAssociate::Element.get_unescaped(review, 'source')
75
+ AmazonAssociate::Element.get_unescaped(review, 'content')
76
+
77
+ # Or this way
78
+ el = AmazonAssociate::Element.new(review)
79
+ el.get_unescaped('source')
80
+ el.get_unescaped('content')
81
+ end
82
+
83
+ # returns AmazonAssociate::Element instead of string
84
+ item.search_and_convert('itemattributes').
85
+ end
86
+
87
+ Refer to Amazon Associate's documentation for more information on Amazon REST request parameters and XML output:
88
+ http://docs.amazonwebservices.com/AWSEcommerceService/2006-09-13/
89
+
90
+ To get a sample of Amazon REST response XML output, use AWSZone.com scratch pad:
91
+ http://www.awszone.com/scratchpads/aws/ecs.us/index.aws
92
+
93
+ == CACHING
94
+
95
+ Filesystem caching is now available.
96
+
97
+ AmazonAssociate::Request.configure do |options|
98
+ options[:aWS_access_key_id] = [your developer token]
99
+ options[:scret_key] = [your secret key]
100
+ options[:caching_strategy] = :filesystem
101
+ options[:caching_options] = {
102
+ :disk_quota => 200,
103
+ :cache_path => <path where you want to store requests>,
104
+ :sweep_frequency => 4
105
+ }
106
+ end
107
+
108
+ The above command will cache up to 200MB of requests. It will purge the cache every 4 hours or when the disk quota has been exceeded.
109
+
110
+ Every request will be stored in the cache path. On every request, AmazonAssociate::Request will check for the presence of the cached file before querying Amazon directly.
111
+
112
+ == LINKS
113
+
114
+ * http://amazon-ecs.rubyforge.org
115
+
116
+ == LICENSE
117
+
118
+ (The MIT License)
119
+
120
+ Copyright (c) 2008 Dan Pickett, Enlight Solutions, Inc.
@@ -0,0 +1,43 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ require "rake/testtask"
4
+ require "rake/rdoctask"
5
+ require "rake/gempackagetask"
6
+ require "date"
7
+
8
+ desc "Run unit tests."
9
+ task :default => :test
10
+
11
+ desc "Test the amazon_associate library."
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << "test"
14
+ t.test_files = FileList["test/**/*test.rb"]
15
+ t.verbose = true
16
+ t.warning = true
17
+ end
18
+
19
+ desc "Generate documentation for the amazon_associate plugin."
20
+ Rake::RDocTask.new(:rdoc) do |rdoc|
21
+ rdoc.rdoc_dir = "rdoc"
22
+ rdoc.title = "amazon_associate"
23
+ rdoc.options << "--line-numbers" << "--inline-source" << "--main" << "README.textile"
24
+ rdoc.rdoc_files.include("README.rdoc", "CHANGELOG")
25
+ rdoc.rdoc_files.include("lib/**/*.rb")
26
+ end
27
+
28
+ begin
29
+ require 'jeweler'
30
+ Jeweler::Tasks.new do |gemspec|
31
+ gemspec.name = "amazon_associate"
32
+ gemspec.summary = "Amazon Associates API Interface using Hpricot"
33
+ gemspec.email = "dpickett@enlightsolutions.com"
34
+ gemspec.homepage = "http://github.com/dpickett/amazon_associate"
35
+ gemspec.description = "interfaces with Amazon Associate's API using Hpricot"
36
+ gemspec.authors = ["Dan Pickett"]
37
+ end
38
+ rescue LoadError
39
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
40
+ end
41
+
42
+ desc "Clean files generated by rake tasks"
43
+ task :clobber => [:clobber_rdoc, :clobber_package]
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 0
4
+ :minor: 7
@@ -0,0 +1,67 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{amazon_associate}
5
+ s.version = "0.7.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Dan Pickett"]
9
+ s.date = %q{2009-08-01}
10
+ s.description = %q{interfaces with Amazon Associate's API using Hpricot}
11
+ s.email = %q{dpickett@enlightsolutions.com}
12
+ s.extra_rdoc_files = [
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".autotest",
17
+ ".gitignore",
18
+ ".project",
19
+ "CHANGELOG",
20
+ "MIT-LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION.yml",
24
+ "amazon_associate.gemspec",
25
+ "lib/amazon_associate.rb",
26
+ "lib/amazon_associate/cache_factory.rb",
27
+ "lib/amazon_associate/caching_strategy.rb",
28
+ "lib/amazon_associate/caching_strategy/base.rb",
29
+ "lib/amazon_associate/caching_strategy/filesystem.rb",
30
+ "lib/amazon_associate/configuration_error.rb",
31
+ "lib/amazon_associate/element.rb",
32
+ "lib/amazon_associate/request.rb",
33
+ "lib/amazon_associate/request_error.rb",
34
+ "lib/amazon_associate/response.rb",
35
+ "test/amazon_associate/browse_node_lookup_test.rb",
36
+ "test/amazon_associate/cache_test.rb",
37
+ "test/amazon_associate/caching_strategy/filesystem_test.rb",
38
+ "test/amazon_associate/cart_test.rb",
39
+ "test/amazon_associate/request_test.rb",
40
+ "test/test_helper.rb",
41
+ "test/utilities/filesystem_test_helper.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/dpickett/amazon_associate}
44
+ s.rdoc_options = ["--charset=UTF-8"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = %q{1.3.5}
47
+ s.summary = %q{Amazon Associates API Interface using Hpricot}
48
+ s.test_files = [
49
+ "test/amazon_associate/browse_node_lookup_test.rb",
50
+ "test/amazon_associate/cache_test.rb",
51
+ "test/amazon_associate/caching_strategy/filesystem_test.rb",
52
+ "test/amazon_associate/cart_test.rb",
53
+ "test/amazon_associate/request_test.rb",
54
+ "test/test_helper.rb",
55
+ "test/utilities/filesystem_test_helper.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ else
64
+ end
65
+ else
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require "base64"
4
+ require "hmac-sha2"
5
+ require "digest/sha1"
6
+ require "digest/sha2"
7
+
8
+ require "amazon_associate/request"
9
+ require "amazon_associate/element"
10
+ require "amazon_associate/response"
11
+ require "amazon_associate/cache_factory"
12
+ require "amazon_associate/caching_strategy"
13
+ require "amazon_associate/configuration_error"
14
+ require "amazon_associate/request_error"
@@ -0,0 +1,31 @@
1
+ module AmazonAssociate
2
+ class CacheFactory
3
+ def self.cache(request, response, strategy)
4
+ strategy_class_hash[strategy].cache(request, response)
5
+ end
6
+
7
+ def self.initialize_options(options)
8
+ #check for a valid caching strategy
9
+ unless self.strategy_class_hash.keys.include?(options[:caching_strategy])
10
+ raise AmazonAssociate::ConfigurationError, "Invalid caching strategy"
11
+ end
12
+
13
+ strategy_class_hash[options[:caching_strategy]].initialize_options(options)
14
+ end
15
+
16
+ def self.get(request, strategy)
17
+ strategy_class_hash[strategy].get(request)
18
+ end
19
+
20
+ def self.sweep(strategy)
21
+ strategy_class_hash[strategy].sweep
22
+ end
23
+
24
+ private
25
+ def self.strategy_class_hash
26
+ {
27
+ :filesystem => AmazonAssociate::CachingStrategy::Filesystem
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,2 @@
1
+ require File.join(File.dirname(__FILE__), "caching_strategy/base")
2
+ require File.join(File.dirname(__FILE__), "caching_strategy/filesystem")
@@ -0,0 +1,22 @@
1
+ #abstract class
2
+ module AmazonAssociate
3
+ module CachingStrategy
4
+ class Base
5
+ def self.cache(request, response)
6
+ raise "This method must be overwritten by a caching strategy"
7
+ end
8
+
9
+ def self.initialize_options(options)
10
+ raise "This method must be overwritten by a caching strategy"
11
+ end
12
+
13
+ def self.get(request)
14
+ raise "This method must be overwritten by a caching strategy"
15
+ end
16
+
17
+ def self.sweep
18
+ raise "This method must be overwritten by a caching strategy"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,109 @@
1
+ require "fileutils"
2
+ require "find"
3
+
4
+ module AmazonAssociate
5
+ module CachingStrategy
6
+ class Filesystem < AmazonAssociate::CachingStrategy::Base
7
+ #disk quota in megabytes
8
+ DEFAULT_DISK_QUOTA = 200
9
+
10
+ #frequency of sweeping in hours
11
+ DEFAULT_SWEEP_FREQUENCY = 2
12
+
13
+ class << self
14
+ attr_accessor :cache_path
15
+ attr_accessor :disk_quota
16
+ attr_accessor :sweep_frequency
17
+
18
+ def cache(request, response)
19
+ path = self.cache_path
20
+ cached_filename = Digest::SHA1.hexdigest(request)
21
+ cached_folder = cached_filename[0..2]
22
+
23
+ FileUtils.mkdir_p(File.join(path, cached_folder, cached_folder))
24
+
25
+ cached_file = File.open(File.join(path, cached_folder, cached_filename), "w")
26
+ cached_file.puts response.doc.to_s
27
+ cached_file.close
28
+ end
29
+
30
+ def get(request)
31
+ path = self.cache_path
32
+ cached_filename = Digest::SHA1.hexdigest(request)
33
+ file_path = File.join(path, cached_filename[0..2], cached_filename)
34
+ if FileTest.exists?(file_path)
35
+ File.read(file_path).chomp
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ def initialize_options(options)
42
+ #check for required options
43
+ if options[:caching_options].nil? || options[:caching_options][:cache_path].nil?
44
+ raise AmazonAssociate::ConfigurationError, "You must specify caching options for filesystem caching: :cache_path is required"
45
+ end
46
+
47
+ #default disk quota to 200MB
48
+ Filesystem.disk_quota = options[:caching_options][:disk_quota] || DEFAULT_DISK_QUOTA
49
+
50
+ Filesystem.sweep_frequency = options[:caching_options][:sweep_frequency] || DEFAULT_SWEEP_FREQUENCY
51
+
52
+ Filesystem.cache_path = options[:caching_options][:cache_path]
53
+
54
+ if Filesystem.cache_path.nil? || !File.directory?(Filesystem.cache_path)
55
+ raise AmazonAssociate::ConfigurationError, "You must specify a cache path for filesystem caching"
56
+ end
57
+ return options
58
+ end
59
+
60
+ def sweep
61
+ perform_sweep if must_sweep?
62
+ end
63
+
64
+ private
65
+ def perform_sweep
66
+ FileUtils.rm_rf(Dir.glob("#{Filesystem.cache_path}/*"))
67
+
68
+ timestamp_sweep_performance
69
+ end
70
+
71
+ def timestamp_sweep_performance
72
+ #remove the timestamp
73
+ FileUtils.rm_rf(timestamp_filename)
74
+
75
+ #create a new one its place
76
+ timestamp = File.open(timestamp_filename, "w")
77
+ timestamp.puts(Time.now)
78
+ timestamp.close
79
+ end
80
+
81
+ def must_sweep?
82
+ sweep_time_expired? || disk_quota_exceeded?
83
+ end
84
+
85
+ def sweep_time_expired?
86
+ FileTest.exists?(timestamp_filename) && Time.parse(File.read(timestamp_filename).chomp) < Time.now - (sweep_frequency * 3600)
87
+ end
88
+
89
+ def disk_quota_exceeded?
90
+ cache_size > Filesystem.disk_quota
91
+ end
92
+
93
+ def timestamp_filename
94
+ File.join(Filesystem.cache_path, ".amz_timestamp")
95
+ end
96
+
97
+ def cache_size
98
+ size = 0
99
+ Find.find(Filesystem.cache_path) do|f|
100
+ size += File.size(f) if File.file?(f)
101
+ end
102
+ size / 1000000
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+ end
109
+ end