diff --git a/Gemfile b/Gemfile index 3578fbdd..ae4640a8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,22 +1,19 @@ source 'http://rubygems.org' gem 'rails', '3.0.0' - gem 'sqlite3-ruby', :require => 'sqlite3' -gem 'haml', '~> 3.0.18' - -gem 'will_paginate', '~> 3.0.pre2' - -gem 'rdiscount', '~> 1.6.5' - gem 'compass', '~> 0.10.1' +gem 'haml', '~> 3.0.18' +gem 'rdiscount', '~> 1.6.5' +gem 'RocketAMF', '~> 0.2.1' +gem 'will_paginate', '~> 3.0.pre2' group :development do gem 'mysql' end group :test do - gem 'rspec-rails', '~> 2.0.0.beta.22' gem 'factory_girl_rails', '~> 1.0' + gem 'rspec-rails', '~> 2.0.0.beta.22' end diff --git a/Gemfile.lock b/Gemfile.lock index b2a42b57..67118966 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,7 @@ GEM remote: http://rubygems.org/ specs: + RocketAMF (0.2.1) abstract (1.0.0) actionmailer (3.0.0) actionpack (= 3.0.0) @@ -92,6 +93,7 @@ PLATFORMS ruby DEPENDENCIES + RocketAMF (~> 0.2.1) compass (~> 0.10.1) factory_girl_rails (~> 1.0) haml (~> 3.0.18) diff --git a/app/models/item.rb b/app/models/item.rb index 34385c20..a96ce859 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -1,8 +1,10 @@ class Item < ActiveRecord::Base - include SwfAssetParent - SwfAssetType = 'object' + has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id', + :conditions => {:swf_asset_type => SwfAssetType} + has_many :swf_assets, :through => :parent_swf_asset_relationships + NCRarities = [0, 500] PaintbrushSetDescription = 'This item is part of a deluxe paint brush set!' @@ -102,6 +104,89 @@ class Item < ActiveRecord::Base } end + def before_save + sold_in_mall = false + end + + def origin_registry_info=(info) + # bear in mind that numbers from registries are floats + self.species_support_ids = info[:species_support].map(&:to_i) + attribute_names.each do |attribute| + value = info[attribute.to_sym] + if value + value = value.to_i if value.is_a? Float + self[attribute] = value + end + end + end + + def self.collection_from_pet_type_and_registries(pet_type, info_registry, asset_registry) + # bear in mind that registries are arrays with many nil elements, + # due to how the parser works + items = {} + item_ids = [] + info_registry.each do |info| + if info + item_ids << info[:obj_info_id].to_i + end + end + existing_relationships_by_item_id_and_swf_asset_id = {} + existing_items = Item.find_all_by_id(item_ids, :include => :parent_swf_asset_relationships) + existing_items.each do |item| + items[item.id] = item + relationships_by_swf_asset_id = {} + item.parent_swf_asset_relationships.each do |relationship| + relationships_by_swf_asset_id[relationship.swf_asset_id] = relationship + end + existing_relationships_by_item_id_and_swf_asset_id[item.id] = + relationships_by_swf_asset_id + end + swf_asset_ids = [] + asset_registry.each_with_index do |asset_data, index| + swf_asset_ids << index if asset_data + end + existing_swf_assets = SwfAsset.find_all_by_id(swf_asset_ids) + existing_swf_assets_by_id = {} + existing_swf_assets.each do |swf_asset| + existing_swf_assets_by_id[swf_asset.id] = swf_asset + end + relationships_by_item_id = {} + asset_registry.each do |asset_data| + if asset_data + item_id = asset_data[:obj_info_id].to_i + item = items[item_id] + unless item + item = Item.new + item.id = item_id + items[item_id] = item + end + item.origin_registry_info = info_registry[item.id] + swf_asset_id = asset_data[:asset_id].to_i + swf_asset = existing_swf_assets[swf_asset_id] + unless swf_asset + swf_asset = SwfAsset.new + swf_asset.id = swf_asset_id + end + swf_asset.origin_object_data = asset_data + swf_asset.origin_pet_type = pet_type + relationship = existing_relationships_by_item_id_and_swf_asset_id[item.id][swf_asset_id] rescue nil + unless relationship + relationship = ParentSwfAssetRelationship.new + relationship.parent_id = item.id + relationship.swf_asset_type = SwfAssetType + relationship.swf_asset_id = swf_asset.id + end + relationship.swf_asset = swf_asset + relationships_by_item_id[item_id] ||= [] + relationships_by_item_id[item_id] << relationship + end + end + relationships_by_item_id.each do |item_id, relationships| + items[item_id].parent_swf_asset_relationships = relationships + end + items.values + end + private SearchFilterScopes = [] diff --git a/app/models/pet.rb b/app/models/pet.rb new file mode 100644 index 00000000..5ea031cb --- /dev/null +++ b/app/models/pet.rb @@ -0,0 +1,55 @@ +class Pet < ActiveRecord::Base + GATEWAY_URL = 'http://www.neopets.com/amfphp/gateway.php' + AMF_SERVICE_NAME = 'CustomPetService' + PET_VIEWER_METHOD = 'getViewerData' + PET_NOT_FOUND_REMOTE_ERROR = 'PHP: Unable to retrieve records from the database.' + + belongs_to :pet_type + + attr_reader :items + + def load! + require 'ostruct' + begin + envelope = Pet.amf_service.fetch(PET_VIEWER_METHOD, name, nil) + rescue RocketAMF::RemoteGateway::AMFError => e + if e.message == PET_NOT_FOUND_REMOTE_ERROR + raise PetNotFound, "Pet #{name.inspect} does not exist" + end + raise + end + contents = OpenStruct.new(envelope.messages[0].data.body) + pet_data = OpenStruct.new(contents.custom_pet) + self.pet_type = PetType.find_or_initialize_by_species_id_and_color_id( + pet_data.species_id.to_i, + pet_data.color_id.to_i + ) + self.pet_type.body_id = pet_data.body_id + @pet_state = self.pet_type.add_pet_state_from_biology! pet_data.biology_by_zone + @items = Item.collection_from_pet_type_and_registries(self.pet_type, + contents.object_info_registry, contents.object_asset_registry) + true + end + + def self.load(name) + pet = Pet.find_or_initialize_by_name(name) + pet.load! + pet + end + + private + + def self.amf_service + @amf_service ||= gateway.service AMF_SERVICE_NAME + end + + def self.gateway + unless @gateway + require 'rocketamf/remote_gateway' + @gateway = RocketAMF::RemoteGateway.new(GATEWAY_URL) + end + @gateway + end + + class PetNotFound < Exception;end +end diff --git a/app/models/pet_state.rb b/app/models/pet_state.rb index 2b6594b0..5f501da7 100644 --- a/app/models/pet_state.rb +++ b/app/models/pet_state.rb @@ -1,4 +1,73 @@ class PetState < ActiveRecord::Base - include SwfAssetParent SwfAssetType = 'biology' + + has_many :parent_swf_asset_relationships, :foreign_key => 'parent_id', + :conditions => {:swf_asset_type => SwfAssetType} + has_many :swf_assets, :through => :parent_swf_asset_relationships + + belongs_to :pet_type + + alias_method :swf_asset_ids_from_association, :swf_asset_ids + + def swf_asset_ids + self['swf_asset_ids'] + end + + def swf_asset_ids=(ids) + self['swf_asset_ids'] = ids + end + + def self.from_pet_type_and_biology_info(pet_type, info) + swf_asset_ids = [] + info.each do |asset_info| + if asset_info + swf_asset_ids << asset_info[:part_id].to_i + end + end + swf_asset_ids_str = swf_asset_ids.join(',') + if pet_type.new_record? + pet_state = self.new :swf_asset_ids => swf_asset_ids_str + else + pet_state = self.find_or_initialize_by_pet_type_id_and_swf_asset_ids( + pet_type.id, + swf_asset_ids_str + ) + end + existing_swf_assets = SwfAsset.find_all_by_id(swf_asset_ids) + existing_swf_assets_by_id = {} + existing_swf_assets.each do |swf_asset| + existing_swf_assets_by_id[swf_asset.id] = swf_asset + end + existing_relationships_by_swf_asset_id = {} + unless pet_state.new_record? + pet_state.parent_swf_asset_relationships.each do |relationship| + existing_relationships_by_swf_asset_id[relationship.swf_asset_id] = relationship + end + end + pet_state.pet_type = pet_type # save the second case from having to look it up by ID + relationships = [] + info.each do |asset_info| + if asset_info + swf_asset_id = asset_info[:part_id].to_i + swf_asset = existing_swf_assets_by_id[swf_asset_id] + unless swf_asset + swf_asset = SwfAsset.new + swf_asset.id = swf_asset_id + end + swf_asset.origin_biology_data = asset_info + swf_asset.origin_pet_type = pet_type + relationship = existing_relationships_by_swf_asset_id[swf_asset_id] + unless relationship + relationship ||= ParentSwfAssetRelationship.new + relationship.parent_id = pet_state.id + relationship.swf_asset_type = SwfAssetType + relationship.swf_asset_id = swf_asset.id + end + relationship.swf_asset = swf_asset + relationships << relationship + end + end + pet_state.parent_swf_asset_relationships = relationships + pet_state + end end diff --git a/app/models/pet_type.rb b/app/models/pet_type.rb index 6be2bf3c..88e00b5f 100644 --- a/app/models/pet_type.rb +++ b/app/models/pet_type.rb @@ -3,7 +3,6 @@ class PetType < ActiveRecord::Base BasicHashes = YAML::load_file(Rails.root.join('config', 'basic_type_hashes.yml')) - StandardBodyIds = PetType.select(arel_table[:body_id]). where(arel_table[:color_id].in(Color::BasicIds)). group(arel_table[:species_id]).map(&:body_id) @@ -55,4 +54,10 @@ class PetType < ActiveRecord::Base def image_hash BasicHashes[species.name][color.name] end + + def add_pet_state_from_biology!(biology) + pet_state = PetState.from_pet_type_and_biology_info(self, biology) + self.pet_states << pet_state + pet_state + end end diff --git a/app/models/swf_asset.rb b/app/models/swf_asset.rb index 23316396..d80fce0e 100644 --- a/app/models/swf_asset.rb +++ b/app/models/swf_asset.rb @@ -33,4 +33,21 @@ class SwfAsset < ActiveRecord::Base def zone @zone ||= Zone.find(zone_id) end + + def origin_pet_type=(pet_type) + self.body_id = pet_type.body_id + end + + def origin_biology_data=(data) + self.type = 'biology' + self.zone_id = data[:zone_id].to_i + self.url = data[:asset_url] + self.zones_restrict = data[:zones_restrict] + end + + def origin_object_data=(data) + self.type = 'object' + self.zone_id = data[:zone_id].to_i + self.url = data[:asset_url] + end end diff --git a/lib/deferred_attributes.rb b/lib/deferred_attributes.rb new file mode 100644 index 00000000..d044d4d3 --- /dev/null +++ b/lib/deferred_attributes.rb @@ -0,0 +1,10 @@ +module DeferredAttributes + def attr_deferred(name, &block) + instance_variable_name = "@#{name}" + define_method name do + value = instance_variable_get(instance_variable_name) + return value if value + instance_variable_set(instance_variable_name, self.instance_eval(&block)) + end + end +end diff --git a/lib/rocketamf/remote_gateway.rb b/lib/rocketamf/remote_gateway.rb new file mode 100644 index 00000000..818a9cda --- /dev/null +++ b/lib/rocketamf/remote_gateway.rb @@ -0,0 +1,18 @@ +require 'net/http' +require 'rocketamf' +require_relative 'remote_gateway/service' +require_relative 'remote_gateway/request' + +module RocketAMF + class RemoteGateway + attr_reader :uri + + def initialize(url) + @uri = URI.parse url + end + + def service(name) + Service.new(self, name) + end + end +end diff --git a/lib/rocketamf/remote_gateway/request.rb b/lib/rocketamf/remote_gateway/request.rb new file mode 100644 index 00000000..508a3aa4 --- /dev/null +++ b/lib/rocketamf/remote_gateway/request.rb @@ -0,0 +1,74 @@ +module RocketAMF + class RemoteGateway + class Request + ERROR_CODE = 'AMFPHP_RUNTIME_ERROR' + + def initialize(service, method, *params) + @service = service + @method = method + @params = params + end + + def fetch + uri = @service.gateway.uri + data = envelope.serialize + + req = Net::HTTP::Post.new(uri.path) + req.body = data + res = Net::HTTP.new(uri.host, uri.port).start { |http| http.request(req) } + case res + when Net::HTTPSuccess, Net::HTTPRedirection + result = RocketAMF::Envelope.new.populate_from_stream(res.body) + first_message_data = result.messages[0].data + if first_message_data.respond_to?(:[]) && first_message_data[:code] == ERROR_CODE + raise AMFError.new(first_message_data) + end + return result + else + error = nil + begin + res.error! + rescue Exception => scoped_error + error = scoped_error + end + raise ConnectionError, "Error connecting to gateway: #{error}" + end + end + + private + + def envelope + output = Envelope.new + output.messages << wrapper_message + output + end + + def wrapper_message + message = Message.new 'null', '/1', [remoting_message] + end + + def remoting_message + message = Values::RemotingMessage.new + message.source = @service.name + message.operation = @method + message.body = @params + message + end + end + + class ConnectionError < RuntimeError;end + class AMFError < RuntimeError + DATA_KEYS = [:details, :line, :code] + attr_reader *DATA_KEYS + attr_reader :message + + def initialize(data) + DATA_KEYS.each do |key| + instance_variable_set "@#{key}", data[key] + end + + @message = data[:description] + end + end + end +end diff --git a/lib/rocketamf/remote_gateway/service.rb b/lib/rocketamf/remote_gateway/service.rb new file mode 100644 index 00000000..a86e6eba --- /dev/null +++ b/lib/rocketamf/remote_gateway/service.rb @@ -0,0 +1,16 @@ +module RocketAMF + class RemoteGateway + class Service + attr_reader :gateway, :name + + def initialize(gateway, name) + @gateway = gateway + @name = name + end + + def fetch(method, *params) + Request.new(self, method, *params).fetch + end + end + end +end diff --git a/test/factories/pet_state.rb b/test/factories/pet_state.rb index 502f7607..299002b4 100644 --- a/test/factories/pet_state.rb +++ b/test/factories/pet_state.rb @@ -1,4 +1,4 @@ Factory.define :pet_state do |ps| ps.pet_type_id 1 - ps.swf_asset_ids '1,2,3' + ps.swf_asset_ids_cache '1,2,3' end diff --git a/vendor/cache/RocketAMF-0.2.1.gem b/vendor/cache/RocketAMF-0.2.1.gem new file mode 100644 index 00000000..dfd9c662 Binary files /dev/null and b/vendor/cache/RocketAMF-0.2.1.gem differ