From a383eb87ce4061dcbe7e056148a8a1a4a061b38c Mon Sep 17 00:00:00 2001 From: Florian Gross Date: Mon, 2 Jul 2012 15:00:39 +0200 Subject: [PATCH] A bunch of fixes for restoring compatibility with current versions of mechanize and the AppStore * AppStore::Application: Get rid of accepted_type, new icon and artwork parsing * AppStore::Artwork: No longer used, gone * AppStore::Client: Distinction between Plists and HTML is now handled by Content-Type, not based on where the request came from (browser or iOS); also make .get() call compatible with newer versions of mechanize * Link: Use itunes_get for apps, fallback to raw data * Add PlistParser class for Mechanize Plist parsing integration * Fix test cases for new versions of mechanize --- lib/app_store/application.rb | 66 ++++++++++++++++++++++++----------- lib/app_store/artwork.rb | 19 ---------- lib/app_store/client.rb | 23 +++++++++--- lib/app_store/link.rb | 2 +- lib/app_store/plist_parser.rb | 20 +++++++++++ test/client_test.rb | 4 +-- 6 files changed, 87 insertions(+), 47 deletions(-) delete mode 100644 lib/app_store/artwork.rb create mode 100644 lib/app_store/plist_parser.rb diff --git a/lib/app_store/application.rb b/lib/app_store/application.rb index b47cc37..ce30ffa 100644 --- a/lib/app_store/application.rb +++ b/lib/app_store/application.rb @@ -1,9 +1,9 @@ require "app_store/base" require "app_store/company" require "app_store/user_review" -require "app_store/artwork" require "app_store/list" require "app_store/link" +require "app_store/image" # Represents an application in the AppStore. # = Available attributes: @@ -54,16 +54,15 @@ class AppStore::Application < AppStore::Base attr_reader :company, :price, :size, :artworks, :icon, :icon_thumbnail, :screenshots - plist :accepted_type => 'software', - :mapping => { - 'average-user-rating' => :average_user_rating, - 'user-rating-count' => :user_rating_count, - 'release-date' => :release_date, - 'description' => :description, - 'item-id' => :item_id, - 'version' => :version, - 'title' => :title - } + plist :mapping => { + 'average-user-rating' => :average_user_rating, + 'user-rating-count' => :user_rating_count, + 'release-date' => :release_date, + 'description' => :description, + 'item-id' => :item_id, + 'version' => :version, + 'title' => :title + } # Search an Application by its id. Accepts only one id and returns an Application instance. def self.find_by_id(id, options = {}) @@ -101,8 +100,10 @@ def itunes_url def icon if @icon.nil? - parsed = @client.itunes_get(AppStore::Client::ApplicationURL, :id => item_id) - @icon = AppStore::Image.new(:plist => parsed.search('PictureView[@height="100"][@width="100"]').first) + parsed = @client.browser_get(AppStore::Client::ApplicationURL, :id => item_id) + + plist = parsed.search('PictureView[@height="100"][@width="100"]').first + @icon = AppStore::Image.new(:plist => plist) if plist end @icon @@ -110,6 +111,8 @@ def icon protected def custom_init_from_plist(plist) + client = AppStore::Client.new + # Set size and price @price = plist['store-offers']['STDQ']['price'] @size = plist['store-offers']['STDQ']['size'] @@ -117,16 +120,39 @@ def custom_init_from_plist(plist) # Seek for company @company = AppStore::Company.new(:plist => plist['company']) + icons = [] + # Parse artwork - @artworks = plist['artwork-urls'].collect do |plist_artwork| - # OPTIMIZE : handle default_screenshot - if plist_artwork['image-type'] and plist_artwork['default'] - artwork = AppStore::Artwork.new :plist => plist_artwork - @icon_thumbnail ||= artwork.default if artwork.is_icon? - @screenshots << artwork.default unless artwork.is_icon? - artwork + @artworks = plist['artwork-urls'].collect do |plist_artwork_p| + if plist_artwork_p.include?("default@2x") + plist_artwork = plist_artwork_p["default@2x"] + elsif plist_artwork_p.include?("default") + plist_artwork = plist_artwork_p["default"] + else + plist_artwork = plist_artwork_p end + + result = AppStore::Image.new :plist => plist_artwork + + icons << result if plist_artwork_p["image-type"] == "software-icon" + + result end + @artworks.compact! + + @icon_thumbnail ||= @artworks.first if @artworks.size > 0 + + if icons.size > 0 + @icon = icons.sort_by { |icon| icon.width * icon.height }.last + + icon_url = @icon.url.dup + icon_url[/\d+x\d+/] = "512x512" + + if icon_url != @icon.url then + result = begin; client.browser_get(icon_url); rescue Mechanize::ResponseCodeError; nil; end + @icon.url.replace(icon_url) if result + end + end end end \ No newline at end of file diff --git a/lib/app_store/artwork.rb b/lib/app_store/artwork.rb deleted file mode 100644 index 0accaba..0000000 --- a/lib/app_store/artwork.rb +++ /dev/null @@ -1,19 +0,0 @@ -require "app_store/base" -require "app_store/image" - -class AppStore::Artwork < AppStore::Base - attr_reader :default, :thumbnail - - plist :mapping => { 'image-type' => :image_type } - - def is_icon? - image_type == 'software-icon' - end - - protected - def custom_init_from_plist(plist) - @default = AppStore::Image.new(:plist => plist['default']) if plist['default'] - @thumbnail = AppStore::Image.new(:plist => plist['thumbnail']) if plist['thumbnail'] - end - -end \ No newline at end of file diff --git a/lib/app_store/client.rb b/lib/app_store/client.rb index bc31074..f9f84f7 100644 --- a/lib/app_store/client.rb +++ b/lib/app_store/client.rb @@ -4,6 +4,7 @@ require "plist" require "app_store" +require "app_store/plist_parser" # Client regroups all the calling and xml parsing mechanism to call the AppStore. class AppStore::Client @@ -44,7 +45,7 @@ def initialize(args = {}) # Call the Apple AppStore using given url and passing params with an HTTP Get method. # Call is seen as a fake iPhone iTunes client. def iphone_get(url, params = {}) - Plist::parse_xml make_call_and_handle_answer(iphone_agent, url, params).body + make_call_and_handle_answer(iphone_agent, url, params) end alias :get :iphone_get @@ -52,16 +53,28 @@ def iphone_get(url, params = {}) # Call the Apple AppStore using given url and passing params with HTTP Get method. # Call is seen as a fake Mac OS X iTunes client. def itunes_get(url, params = {}) - Nokogiri.parse make_call_and_handle_answer(itunes_agent, url, params).body + make_call_and_handle_answer(itunes_agent, url, params) + end + + def browser_get(url, params = {}) + make_call_and_handle_answer(browser_agent, url, params) end protected + def setup_agent(agent) + agent.pluggable_parser['text/xml'] = PlistParser + end + def iphone_agent - @iphone_agent ||= WWW::Mechanize.new { |a| a.user_agent = 'iTunes-iPhone/3.0 (2)' } + @iphone_agent ||= Mechanize.new { |a| setup_agent(a); a.user_agent = 'iTunes-iPhone/3.0 (2)' } end def itunes_agent - @itunes_agent ||= WWW::Mechanize.new { |a| a.user_agent = 'iTunes/9.0.1 (Macintosh; Intel Mac OS X 10.6.1) AppleWebKit/531.9' } + @itunes_agent ||= Mechanize.new { |a| setup_agent(a); a.user_agent = 'iTunes/9.0.1 (Macintosh; Intel Mac OS X 10.6.1) AppleWebKit/531.9' } + end + + def browser_agent + @itunes_agent ||= Mechanize.new { |a| setup_agent(a); a.user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7.4) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5' } end def headers @@ -74,7 +87,7 @@ def headers # Make call using given agent, url and params. # Handle answer code and returns answer if answer code was correct. def make_call_and_handle_answer(agent, url, params) - answer = agent.get(:url => url, :headers => headers, :params => params) + answer = agent.get(url, params, nil, headers) raise AppStore::RequestError if answer.code.to_s != '200' answer end diff --git a/lib/app_store/link.rb b/lib/app_store/link.rb index 3cdec18..444a145 100644 --- a/lib/app_store/link.rb +++ b/lib/app_store/link.rb @@ -11,7 +11,7 @@ class AppStore::Link < AppStore::Base def destination @destination ||= case @item_type when 'software' - AppStore::Application.new :plist => @client.get(@url)['item-metadata'], + AppStore::Application.new :plist => @client.itunes_get(@url)['item-metadata'] || @raw, :client => @client else raise 'unsupported' diff --git a/lib/app_store/plist_parser.rb b/lib/app_store/plist_parser.rb new file mode 100644 index 0000000..8ee42ea --- /dev/null +++ b/lib/app_store/plist_parser.rb @@ -0,0 +1,20 @@ +require 'mechanize' + +class PlistParser < Mechanize::File + extend Forwardable + + attr_accessor :mech, :plist + + def initialize(uri=nil, response=nil, body=nil, code=nil, mech=nil) + raise 'no' if mech and not Mechanize === mech + @mech = mech + + super uri, response, body, code + + @plist = @body ? Plist::parse_xml(@body) : nil + end + + def_delegator :plist, :[], :[] + def_delegator :plist, :search, :search +end + \ No newline at end of file diff --git a/test/client_test.rb b/test/client_test.rb index 89271c5..5756435 100644 --- a/test/client_test.rb +++ b/test/client_test.rb @@ -10,7 +10,7 @@ class ClientTest < Test::Unit::TestCase end should "returns a mechanize agent on call to iphone_agent" do - assert_kind_of WWW::Mechanize, @client.iphone_agent + assert_kind_of Mechanize, @client.iphone_agent end should "set mechanize user agent to iTunes-iPhone/3.0 (2) for iphone_agent" do @@ -18,7 +18,7 @@ class ClientTest < Test::Unit::TestCase end should "returns a mechanize agent on call to itunes_agent" do - assert_kind_of WWW::Mechanize, @client.itunes_agent + assert_kind_of Mechanize, @client.itunes_agent end should "set mechanize user agent to iTunes/9.0.1 (Macintosh; Intel Mac OS X 10.6.1) AppleWebKit/531.9 for itunes_agent" do