From 225f7847327f86323d1852f07aa4003dd50346c1 Mon Sep 17 00:00:00 2001 From: Matchu Date: Wed, 2 Aug 2023 12:51:10 -0700 Subject: [PATCH] Remove outfit image saving This has already been moved to Impress 2020 too, so we can delete all the image generation and saving! --- app/models/outfit.rb | 140 ------------------------------ app/models/outfit_image_update.rb | 21 ----- lib/tasks/outfits.rake | 11 --- 3 files changed, 172 deletions(-) delete mode 100644 app/models/outfit_image_update.rb delete mode 100644 lib/tasks/outfits.rake diff --git a/app/models/outfit.rb b/app/models/outfit.rb index 994d11db..1c1ad83a 100644 --- a/app/models/outfit.rb +++ b/app/models/outfit.rb @@ -13,13 +13,6 @@ class Outfit < ActiveRecord::Base scope :wardrobe_order, -> { order('starred DESC', :name) } - # NOTE: We no longer save images, but we've left the code here for now. - # The `image` method below simulates the previous API for the rest - # of the app! - # mount_uploader :image, OutfitImageUploader - # before_save :update_enqueued_image - # after_commit :enqueue_image! - class OutfitImage def initialize(image_versions) @image_versions = image_versions @@ -126,49 +119,6 @@ class Outfit < ActiveRecord::Base end self.item_outfit_relationships = new_rels end - - # Returns the array of SwfAssets representing each layer of the output image, - # ordered from bottom to top. Careful: this method is memoized, so if the - # image layers change after its first call we'll get bad results. - def image_layers - @image_layers ||= visible_assets_with_images.sort { |a, b| a.depth <=> b.depth } - end - - # Creates and writes the thumbnail images for this outfit iff the new image - # would be different than the current one. (Writes to file in development, - # S3 in production.) If the image is updated, updates the image layers hash - # and runs #save! on the record, so any other changes will also be saved. - def write_image! - if image_layers_dirty? - image = Tempfile.open(['outfit_image', '.png']) - begin - create_image! image - self.image_layers_hash = generate_image_layers_hash - self.image = image - self.image_enqueued = false - save! - ensure - image.close! - end - end - - self.image - end - - # Enqueue an image write iff the new image would be different than the - # current one. - def enqueue_image! - Resque.enqueue(OutfitImageUpdate, id) - end - - def update_enqueued_image - self.image_enqueued = (image_layers_dirty?) - true - end - - def s3_key(size) - URI.encode("#{id}/#{size.join 'x'}.png") - end def self.build_for_user(user, params) Outfit.new.tap do |outfit| @@ -183,94 +133,4 @@ class Outfit < ActiveRecord::Base outfit.attributes = params end end - - protected - - # Creates a 600x600 PNG image of this outfit, writing to the given output - # file. - def create_image!(output) - unless image_layers.empty? - temp_image_files = Parallel.map(image_layers, :in_threads => 8) do |swf_asset| - image_file = Tempfile.open(['outfit_layer', '.png']) - begin - write_temp_swf_asset_image!(swf_asset, image_file) - rescue RightAws::AwsError - nil # skip broken images - else - image_file - ensure - image_file.close - end - end.compact # remove nils for broken images - - # Here we do some awkwardness to get the exact ImageMagick command we - # want, though it's still less awkward than handling the command - # ourselves. Give all of the temporary images as input, flatten them and - # write them to the output path. - command = MiniMagick::CommandBuilder.new('convert') - temp_image_files.each { |image_file| command.push image_file.path } - command.layers 'flatten' - command.push output.path - - # Though the above command really is sufficient, we still need a dummy - # image to handle execution. - output_image = MiniMagick::Image.new(output.path) - output_image.run(command) - - temp_image_files.each(&:unlink) - else - output.close - end - end - - def visible_assets - biology_assets = pet_state.swf_assets.includes(:zone) - object_assets = SwfAsset.object_assets. - fitting_body_id(pet_state.pet_type.body_id).for_item_ids(worn_item_ids). - includes(:zone) - - # Now for fun with bitmasks! Rather than building a bunch of integer arrays - # here, we instead go low-level and use bit-level operations. Build the - # bitmask by parsing the binary string (reversing it to get the lower zone - # numbers on the right), then OR them all together to get the mask - # representing all the restricted zones. (Note to self: why not just store - # in this format in the first place?) - restrictors = biology_assets + worn_items - restricted_zones_mask = restrictors.inject(0) do |mask, restrictor| - mask | restrictor.zones_restrict.reverse.to_i(2) - end - - # Now, check each asset's zone is not restricted in the bitmask using - # bitwise operations: shift 1 to the zone_id position, then AND it with - # the restricted zones mask. If we get 0, then the bit for that zone ID was - # not turned on, so the zone is not restricted and this asset is visible. - all_assets = biology_assets + object_assets - all_assets.select { |a| (1 << (a.zone_id - 1)) & restricted_zones_mask == 0 } - end - - def visible_assets_with_images - visible_assets.select(&:has_image?) - end - - # Generate 8-char hex digest representing visible image layers for this outfit. - # Hash function should be decently collision-resistant. - def generate_image_layers_hash - @generated_image_layers_hash ||= - Digest::MD5.hexdigest(image_layers.map(&:id).join(',')).first(8) - end - - def image_layers_dirty? - generate_image_layers_hash != self.image_layers_hash - end - - IMAGE_BASE_SIZE = [600, 600] - def write_temp_swf_asset_image!(swf_asset, file) - key = swf_asset.s3_key(IMAGE_BASE_SIZE) - bucket = SwfAsset::IMAGE_BUCKET - data = bucket.get(key) - file.binmode # write in binary mode - file.truncate(0) # clear the file - file.write data # write the new data - end end - diff --git a/app/models/outfit_image_update.rb b/app/models/outfit_image_update.rb deleted file mode 100644 index 2fc4bc5b..00000000 --- a/app/models/outfit_image_update.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'timeout' - -class OutfitImageUpdate - TIMEOUT_IN_SECONDS = 30 - - @queue = :outfit_image_updates - - def self.perform(id) - Timeout::timeout(TIMEOUT_IN_SECONDS) do - Outfit.find(id).write_image! - end - end - - # Represents an outfit image update for an outfit that existed before this - # feature was built. Its queue has a lower priority, so new outfits will - # be updated before retroactively converted outfits. - class Retroactive < OutfitImageUpdate - @queue = :retroactive_outfit_image_updates - end -end - diff --git a/lib/tasks/outfits.rake b/lib/tasks/outfits.rake deleted file mode 100644 index 93a03dd3..00000000 --- a/lib/tasks/outfits.rake +++ /dev/null @@ -1,11 +0,0 @@ -namespace :outfits do - desc 'Retroactively enqueue image updates for outfits saved to user accounts' - task :retroactively_enqueue => :environment do - outfits = Outfit.select([:id]).where('image IS NULL AND user_id IS NOT NULL') - puts "Enqueuing #{outfits.count} outfits" - outfits.find_each do |outfit| - Resque.enqueue(OutfitImageUpdate::Retroactive, outfit.id) - end - puts "Successfully enqueued." - end -end