2011-05-20 16:19:14 -07:00
|
|
|
require 'fileutils'
|
|
|
|
require 'uri'
|
2011-06-04 15:40:15 -07:00
|
|
|
require 'utf8'
|
2011-05-20 16:19:14 -07:00
|
|
|
|
2010-05-16 12:01:38 -07:00
|
|
|
class SwfAsset < ActiveRecord::Base
|
2010-10-09 07:53:58 -07:00
|
|
|
PUBLIC_ASSET_DIR = File.join('swfs', 'outfit')
|
|
|
|
LOCAL_ASSET_DIR = Rails.root.join('public', PUBLIC_ASSET_DIR)
|
2011-05-20 16:19:14 -07:00
|
|
|
IMAGE_BUCKET = IMPRESS_S3.bucket('impress-asset-images')
|
|
|
|
IMAGE_PERMISSION = 'public-read'
|
|
|
|
IMAGE_HEADERS = {
|
|
|
|
'Cache-Control' => 'max-age=315360000',
|
|
|
|
'Content-Type' => 'image/png'
|
|
|
|
}
|
2010-11-27 15:41:06 -08:00
|
|
|
NEOPETS_ASSET_SERVER = 'http://images.neopets.com'
|
2011-05-13 05:00:34 -07:00
|
|
|
|
2010-05-16 12:01:38 -07:00
|
|
|
set_inheritance_column 'inheritance_type'
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2012-07-16 13:34:44 -07:00
|
|
|
IMAGE_SIZES = {
|
|
|
|
:small => [150, 150],
|
|
|
|
:medium => [300, 300],
|
|
|
|
:large => [600, 600]
|
|
|
|
}
|
|
|
|
|
2011-05-13 05:00:34 -07:00
|
|
|
include SwfConverter
|
2012-07-16 13:34:44 -07:00
|
|
|
converts_swfs :size => IMAGE_SIZES[:large], :output_sizes => IMAGE_SIZES.values
|
2011-05-13 05:00:34 -07:00
|
|
|
|
|
|
|
def local_swf_path
|
|
|
|
LOCAL_ASSET_DIR.join(local_path_within_outfit_swfs)
|
|
|
|
end
|
|
|
|
|
2011-06-10 11:45:33 -07:00
|
|
|
def swf_image_dir
|
|
|
|
@swf_image_dir ||= Rails.root.join('tmp', 'asset_images_before_upload', self.id.to_s)
|
|
|
|
end
|
|
|
|
|
2011-05-13 05:00:34 -07:00
|
|
|
def swf_image_path(size)
|
2011-06-10 11:45:33 -07:00
|
|
|
swf_image_dir.join("#{size.join 'x'}.png")
|
2011-05-20 16:19:14 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
def after_swf_conversion(images)
|
|
|
|
images.each do |size, path|
|
2011-05-22 13:30:02 -07:00
|
|
|
key = s3_key(size)
|
|
|
|
print "Uploading #{key}..."
|
2011-05-20 16:19:14 -07:00
|
|
|
IMAGE_BUCKET.put(
|
2011-05-22 13:30:02 -07:00
|
|
|
key,
|
2011-05-20 16:19:14 -07:00
|
|
|
File.open(path),
|
|
|
|
{}, # meta headers
|
|
|
|
IMAGE_PERMISSION, # permission
|
|
|
|
IMAGE_HEADERS
|
|
|
|
)
|
|
|
|
puts "done."
|
|
|
|
|
|
|
|
FileUtils.rm path
|
|
|
|
end
|
2011-06-10 11:45:33 -07:00
|
|
|
FileUtils.rmdir swf_image_dir
|
2011-08-07 15:23:44 -07:00
|
|
|
|
|
|
|
self.converted_at = Time.now
|
|
|
|
self.has_image = true
|
|
|
|
self.save!
|
2011-05-20 16:19:14 -07:00
|
|
|
end
|
|
|
|
|
2011-05-22 13:30:02 -07:00
|
|
|
def s3_key(size)
|
|
|
|
URI.encode("#{s3_path}/#{size.join 'x'}.png")
|
|
|
|
end
|
|
|
|
|
|
|
|
def s3_path
|
2012-01-12 15:17:59 -08:00
|
|
|
"#{self['type']}/#{s3_partition_path}#{self.remote_id}"
|
2011-08-07 15:23:44 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
def s3_url(size)
|
|
|
|
"#{IMAGE_BUCKET.public_link}/#{s3_path}/#{size.join 'x'}.png"
|
2011-05-22 13:30:02 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
PARTITION_COUNT = 3
|
|
|
|
PARTITION_DIGITS = 3
|
|
|
|
PARTITION_ID_LENGTH = PARTITION_COUNT * PARTITION_DIGITS
|
|
|
|
def s3_partition_path
|
2012-01-12 15:17:59 -08:00
|
|
|
(remote_id / 10**PARTITION_DIGITS).to_s.rjust(PARTITION_ID_LENGTH, '0').tap do |id_str|
|
2011-05-22 13:30:02 -07:00
|
|
|
PARTITION_COUNT.times do |n|
|
|
|
|
id_str.insert(PARTITION_ID_LENGTH - (n * PARTITION_DIGITS), '/')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2012-07-16 13:34:44 -07:00
|
|
|
|
|
|
|
def image_version
|
|
|
|
converted_at.to_i
|
|
|
|
end
|
|
|
|
|
|
|
|
def image_url(size=IMAGE_SIZES[:large])
|
|
|
|
host = ASSET_HOSTS[:swf_asset_images]
|
|
|
|
size_key = size.join('x')
|
|
|
|
|
|
|
|
"http://#{host}/#{s3_path}/#{size_key}.png?#{image_version}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def images
|
|
|
|
IMAGE_SIZES.values.map { |size| {:size => size, :url => image_url(size)} }
|
|
|
|
end
|
2011-05-22 13:30:02 -07:00
|
|
|
|
2011-05-20 16:19:14 -07:00
|
|
|
def convert_swf_if_not_converted!
|
2011-08-07 15:23:44 -07:00
|
|
|
if needs_conversion?
|
2011-05-20 16:19:14 -07:00
|
|
|
convert_swf!
|
|
|
|
true
|
2011-08-07 15:23:44 -07:00
|
|
|
else
|
|
|
|
false
|
2011-05-20 16:19:14 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-05-22 10:10:29 -07:00
|
|
|
def request_image_conversion!
|
2011-05-20 16:19:14 -07:00
|
|
|
if image_requested?
|
|
|
|
false
|
|
|
|
else
|
2012-01-12 15:17:59 -08:00
|
|
|
Resque.enqueue(AssetImageConversionRequest, self.id)
|
2011-05-20 16:19:14 -07:00
|
|
|
self.image_requested = true
|
|
|
|
save!
|
|
|
|
true
|
|
|
|
end
|
2011-05-13 05:00:34 -07:00
|
|
|
end
|
|
|
|
|
2011-08-07 15:23:44 -07:00
|
|
|
def report_broken
|
|
|
|
if image_pending_repair?
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2012-01-12 15:17:59 -08:00
|
|
|
Resque.enqueue(AssetImageConversionRequest::OnBrokenImageReport, self.id)
|
2011-08-07 15:23:44 -07:00
|
|
|
self.reported_broken_at = Time.now
|
|
|
|
self.save
|
|
|
|
end
|
|
|
|
|
|
|
|
def needs_conversion?
|
|
|
|
!has_image? || image_pending_repair?
|
|
|
|
end
|
|
|
|
|
2011-08-07 17:43:42 -07:00
|
|
|
REPAIR_PENDING_EXPIRES = 1.hour
|
2011-08-07 15:23:44 -07:00
|
|
|
def image_pending_repair?
|
2011-08-07 17:43:42 -07:00
|
|
|
reported_broken_at &&
|
|
|
|
(converted_at.nil? || reported_broken_at > converted_at) &&
|
|
|
|
reported_broken_at > REPAIR_PENDING_EXPIRES.ago
|
2011-08-07 15:23:44 -07:00
|
|
|
end
|
|
|
|
|
2010-11-06 08:52:58 -07:00
|
|
|
attr_accessor :item
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-11-06 15:08:42 -07:00
|
|
|
has_one :contribution, :as => :contributed
|
2012-01-12 15:17:59 -08:00
|
|
|
has_many :parent_swf_asset_relationships
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-05-20 16:04:56 -07:00
|
|
|
delegate :depth, :to => :zone
|
2011-09-06 09:15:09 -07:00
|
|
|
|
|
|
|
def self.body_ids_fitting_standard
|
|
|
|
@body_ids_fitting_standard ||= PetType.standard_body_ids + [0]
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-06-08 15:26:42 -07:00
|
|
|
scope :fitting_body_id, lambda { |body_id|
|
|
|
|
where(arel_table[:body_id].in([body_id, 0]))
|
|
|
|
}
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-06-08 15:26:42 -07:00
|
|
|
scope :fitting_standard_body_ids, lambda {
|
2011-09-06 09:15:09 -07:00
|
|
|
where(arel_table[:body_id].in(body_ids_fitting_standard))
|
2010-06-07 13:33:43 -07:00
|
|
|
}
|
2011-05-02 15:07:56 -07:00
|
|
|
|
|
|
|
scope :fitting_color, lambda { |color|
|
|
|
|
body_ids = PetType.select(:body_id).where(:color_id => color.id).map(&:body_id)
|
|
|
|
where(arel_table[:body_id].in(body_ids))
|
|
|
|
}
|
|
|
|
|
2011-09-06 09:15:09 -07:00
|
|
|
scope :biology_assets, where(:type => PetState::SwfAssetType)
|
|
|
|
scope :object_assets, where(:type => Item::SwfAssetType)
|
2010-11-25 18:33:34 -08:00
|
|
|
scope :for_item_ids, lambda { |item_ids|
|
2012-01-12 15:17:59 -08:00
|
|
|
joins(:parent_swf_asset_relationships).
|
2010-11-25 18:33:34 -08:00
|
|
|
where(ParentSwfAssetRelationship.arel_table[:parent_id].in(item_ids))
|
|
|
|
}
|
wardrobe now considers item.species_support_ids when deciding compatibility
For example, the Meerca Maid Tray is a foreground item, so the SWF is marked
as compatible with all body types, but the item itself is clearly marked as
Meercas-only. items#show reflected this properly, but the swf_assets#index
call that the wardrobe uses ignored item.species_support_ids.
So, /bodies/:body_id/swf_assets.json?item_ids[]=... was deprecated in favor
of /pet_types/:pet_type_id/items/swf_assets.json?item_ids=[]..., which is
much like the former route but, before loading assets, also loads the pet
type and items, then filters the items by compatibility, then only loads
assets for the compatible items.
2013-01-02 20:15:32 -08:00
|
|
|
scope :with_parent_ids, lambda {
|
|
|
|
select('swf_assets.*, parents_swf_assets.parent_id')
|
|
|
|
}
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-05-20 16:04:56 -07:00
|
|
|
def local_url
|
2010-10-09 07:53:58 -07:00
|
|
|
'/' + File.join(PUBLIC_ASSET_DIR, local_path_within_outfit_swfs)
|
2010-05-20 16:04:56 -07:00
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-05-20 16:56:08 -07:00
|
|
|
def as_json(options={})
|
2010-10-10 19:18:42 -07:00
|
|
|
json = {
|
2012-01-12 15:17:59 -08:00
|
|
|
:id => remote_id,
|
2011-05-22 13:30:02 -07:00
|
|
|
:type => type,
|
2010-05-20 16:04:56 -07:00
|
|
|
:depth => depth,
|
2010-06-07 16:50:49 -07:00
|
|
|
:body_id => body_id,
|
2010-10-10 19:18:42 -07:00
|
|
|
:zone_id => zone_id,
|
|
|
|
:zones_restrict => zones_restrict,
|
2011-05-20 16:19:14 -07:00
|
|
|
:is_body_specific => body_specific?,
|
2011-05-22 13:30:02 -07:00
|
|
|
:has_image => has_image?,
|
2012-07-16 13:34:44 -07:00
|
|
|
:images => images
|
2010-05-20 16:04:56 -07:00
|
|
|
}
|
2010-10-10 19:18:42 -07:00
|
|
|
if options[:for] == 'wardrobe'
|
|
|
|
json[:local_path] = local_url
|
|
|
|
else
|
|
|
|
json[:local_url] = local_url
|
|
|
|
end
|
|
|
|
json[:parent_id] = options[:parent_id] if options[:parent_id]
|
|
|
|
json
|
2010-05-20 16:04:56 -07:00
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-10-09 08:23:59 -07:00
|
|
|
def body_specific?
|
2012-10-05 18:56:52 -07:00
|
|
|
# If we already have assigned this a non-zero body id, or if the asset is
|
|
|
|
# in a body-specific zone, or if the item is explicitly labeled as
|
|
|
|
# body-specific (like Encased In Ice, which is body-specific but whose
|
|
|
|
# assets occupy Background Item), then this asset is body-specific.
|
|
|
|
(body_id? && body_id > 0) || self.zone.type_id < 3 || (@item && @item.explicitly_body_specific?)
|
2010-10-09 08:23:59 -07:00
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-06-09 19:56:47 -07:00
|
|
|
def zone
|
2010-10-09 08:23:59 -07:00
|
|
|
Zone.find(zone_id)
|
2010-06-09 19:56:47 -07:00
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-10-07 07:46:23 -07:00
|
|
|
def origin_pet_type=(pet_type)
|
|
|
|
self.body_id = pet_type.body_id
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-10-07 07:46:23 -07:00
|
|
|
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
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-10-07 07:46:23 -07:00
|
|
|
def origin_object_data=(data)
|
|
|
|
self.type = 'object'
|
|
|
|
self.zone_id = data[:zone_id].to_i
|
|
|
|
self.url = data[:asset_url]
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-11-27 15:41:06 -08:00
|
|
|
def mall_data=(data)
|
|
|
|
self.zone_id = data['zone'].to_i
|
|
|
|
self.url = "#{NEOPETS_ASSET_SERVER}/#{data['url']}"
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-10-10 11:43:01 -07:00
|
|
|
before_create do
|
2010-10-09 07:53:58 -07:00
|
|
|
uri = URI.parse url
|
2011-02-19 19:49:13 -08:00
|
|
|
begin
|
|
|
|
response = Net::HTTP.get_response(uri)
|
|
|
|
rescue Exception => e
|
|
|
|
raise DownloadError, e.message
|
|
|
|
end
|
2010-10-09 07:53:58 -07:00
|
|
|
if response.is_a? Net::HTTPSuccess
|
|
|
|
new_local_path = File.join(LOCAL_ASSET_DIR, local_path_within_outfit_swfs)
|
|
|
|
new_local_dir = File.dirname new_local_path
|
2011-06-04 15:40:15 -07:00
|
|
|
content = +response.body
|
2010-10-09 07:53:58 -07:00
|
|
|
FileUtils.mkdir_p new_local_dir
|
|
|
|
File.open(new_local_path, 'w') do |f|
|
|
|
|
f.print content
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
response.error!
|
|
|
|
rescue Exception => e
|
2011-02-19 19:09:12 -08:00
|
|
|
raise DownloadError, "Error loading SWF at #{url}: #{e.message}"
|
2010-10-09 07:53:58 -07:00
|
|
|
else
|
2011-02-19 19:09:12 -08:00
|
|
|
raise DownloadError, "Error loading SWF at #{url}. Response: #{response.inspect}"
|
2010-10-09 07:53:58 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-10-10 11:43:01 -07:00
|
|
|
before_save do
|
2010-10-09 08:23:59 -07:00
|
|
|
# If an asset body ID changes, that means more than one body ID has been
|
|
|
|
# linked to it, meaning that it's probably wearable by all bodies.
|
2010-10-10 19:18:42 -07:00
|
|
|
self.body_id = 0 if !self.body_specific? || (!self.new_record? && self.body_id_changed?)
|
2010-10-09 08:23:59 -07:00
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2011-05-22 13:30:02 -07:00
|
|
|
after_commit :on => :create do
|
2012-01-12 15:17:59 -08:00
|
|
|
Resque.enqueue(AssetImageConversionRequest::OnCreation, self.id)
|
2011-05-20 16:19:14 -07:00
|
|
|
end
|
|
|
|
|
2011-02-19 19:09:12 -08:00
|
|
|
class DownloadError < Exception;end
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-10-09 07:53:58 -07:00
|
|
|
private
|
2011-05-02 15:07:56 -07:00
|
|
|
|
2010-10-09 07:53:58 -07:00
|
|
|
def local_path_within_outfit_swfs
|
|
|
|
uri = URI.parse(url)
|
|
|
|
pieces = uri.path.split('/')
|
|
|
|
relevant_pieces = pieces[4..7]
|
|
|
|
relevant_pieces.unshift pieces[2]
|
|
|
|
File.join(relevant_pieces)
|
|
|
|
end
|
2010-05-16 12:01:38 -07:00
|
|
|
end
|
2011-05-02 15:07:56 -07:00
|
|
|
|