impress/app/models/swf_asset.rb

322 lines
8.8 KiB
Ruby
Raw Normal View History

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
class SwfAsset < ActiveRecord::Base
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'
}
# This is the URL origin we should use when loading from images.neopets.com.
# It can be overridden in .env as `NEOPETS_IMAGES_URL_ORIGIN`, to use our
# asset proxy instead.
NEOPETS_IMAGES_URL_ORIGIN = ENV['NEOPETS_IMAGES_URL_ORIGIN'] || 'http://images.neopets.com'
2011-05-13 05:00:34 -07:00
set_inheritance_column 'inheritance_type'
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
2013-01-21 17:34:39 -08:00
belongs_to :zone
scope :includes_depth, lambda { includes(:zone) }
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|
key = s3_key(size)
print "Uploading #{key}..."
2011-05-20 16:19:14 -07:00
IMAGE_BUCKET.put(
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
def s3_key(size)
URI.encode("#{s3_path}/#{size.join 'x'}.png")
end
def s3_path
"#{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"
end
PARTITION_COUNT = 3
PARTITION_DIGITS = 3
PARTITION_ID_LENGTH = PARTITION_COUNT * PARTITION_DIGITS
def s3_partition_path
(remote_id / 10**PARTITION_DIGITS).to_s.rjust(PARTITION_ID_LENGTH, '0').tap do |id_str|
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')
2015-08-05 17:22:23 -07:00
"//#{host}/#{s3_path}/#{size_key}.png?#{image_version}"
2012-07-16 13:34:44 -07:00
end
def images
IMAGE_SIZES.values.map { |size| {:size => size, :url => image_url(size)} }
end
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
def request_image_conversion!
2011-05-20 16:19:14 -07:00
if image_requested?
false
else
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
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
has_one :contribution, :as => :contributed, :inverse_of => :contributed
has_many :parent_swf_asset_relationships
delegate :depth, :to => :zone
def self.body_ids_fitting_standard
@body_ids_fitting_standard ||= PetType.standard_body_ids + [0]
end
scope :fitting_body_id, lambda { |body_id|
where(arel_table[:body_id].in([body_id, 0]))
}
scope :fitting_standard_body_ids, lambda {
where(arel_table[:body_id].in(body_ids_fitting_standard))
}
scope :fitting_color, lambda { |color|
body_ids = PetType.select(:body_id).where(:color_id => color.id).map(&:body_id)
body_ids << 0
where(arel_table[:body_id].in(body_ids))
}
scope :biology_assets, where(:type => PetState::SwfAssetType)
scope :object_assets, where(:type => Item::SwfAssetType)
scope :for_item_ids, lambda { |item_ids|
joins(:parent_swf_asset_relationships).
where(ParentSwfAssetRelationship.arel_table[:parent_id].in(item_ids))
}
scope :with_parent_ids, lambda {
select('swf_assets.*, parents_swf_assets.parent_id')
}
# To manually change the body ID without triggering the usual change to 0,
# use this override method.
def override_body_id(new_body_id)
@body_id_overridden = true
self.body_id = new_body_id
end
def local_url
'/' + File.join(PUBLIC_ASSET_DIR, local_path_within_outfit_swfs)
end
2010-05-20 16:56:08 -07:00
def as_json(options={})
2010-10-10 19:18:42 -07:00
json = {
:id => remote_id,
:type => type,
: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?,
:has_image => has_image?,
2012-07-16 13:34:44 -07:00
:images => images
}
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
end
2010-10-09 08:23:59 -07:00
def body_specific?
self.zone.type_id < 3 || item_is_body_specific?
end
def item_is_body_specific?
# Get items that we're already bound to in the database, and
# also the one passed to us from the current modeling operation,
# if any.
#
# NOTE: I know this has perf impact... it would be better for
# modeling to preload this probably? But oh well!
items = parent_swf_asset_relationships.includes(:parent).where(parent_type: "Item").map { |r| r.parent }
items << item if item
# Return whether any of them is known to be body-specific.
# This ensures that we always respect the explicitly_body_specific flag!
return items.any? { |i| i.body_specific? }
2010-10-09 08:23:59 -07:00
end
def origin_pet_type=(pet_type)
self.body_id = pet_type.body_id
end
def origin_biology_data=(data)
2013-03-05 13:10:25 -08:00
Rails.logger.debug("my biology data is: #{data.inspect}")
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)
2013-03-05 13:10:25 -08:00
Rails.logger.debug("my object data is: #{data.inspect}")
self.type = 'object'
self.zone_id = data[:zone_id].to_i
self.url = data[:asset_url]
end
2010-11-27 15:41:06 -08:00
def mall_data=(data)
self.zone_id = data['zone'].to_i
self.url = "https://images.neopets.com/#{data['url']}"
2010-11-27 15:41:06 -08:00
end
2015-05-03 14:57:42 -07:00
def self.from_wardrobe_link_params(ids)
where((
arel_table[:remote_id].in(ids[:biology]).and(arel_table[:type].eq('biology'))
).or(
arel_table[:remote_id].in(ids[:object]).and(arel_table[:type].eq('object'))
))
end
before_create do
# HACK: images.neopets.com no longer accepts requests over `http://`, and
# our dependencies don't support the version of HTTPS they want. So,
# we replace images.neopets.com with the NEOPETS_IMAGES_URL_ORIGIN
# specified in the secret `.env` file. (At time of writing, that's
# our proxy: `http://images.neopets-asset-proxy.openneo.net`.)
modified_url = url.sub(/^https?:\/\/images.neopets.com/, NEOPETS_IMAGES_URL_ORIGIN)
uri = URI.parse(modified_url)
begin
http = Net::HTTP.new(uri.host, uri.port)
response = http.get(uri.request_uri)
rescue Exception => e
raise DownloadError, e.message
end
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
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
raise DownloadError, "Error loading SWF at #{url}: #{e.message}"
else
raise DownloadError, "Error loading SWF at #{url}. Response: #{response.inspect}"
end
end
end
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.
self.body_id = 0 if !@body_id_overridden && (!self.body_specific? || (!self.new_record? && self.body_id_changed?))
2010-10-09 08:23:59 -07:00
end
after_commit :on => :create do
Resque.enqueue(AssetImageConversionRequest::OnCreation, self.id)
2011-05-20 16:19:14 -07:00
end
class DownloadError < Exception;end
private
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
end