More embed image bandaids (#175)
* Only re-index search when records are updated * Add logs and fixes * Add preview_s3_key to Parties * Add some extra packages to Nixfile * Add logging around custom font use
This commit is contained in:
parent
1c1ed0dd9d
commit
ad2e2cc028
9 changed files with 189 additions and 62 deletions
|
|
@ -13,7 +13,10 @@ class GeneratePartyPreviewJob < ApplicationJob
|
||||||
|
|
||||||
def perform(party_id)
|
def perform(party_id)
|
||||||
Rails.logger.info("Starting preview generation for party #{party_id}")
|
Rails.logger.info("Starting preview generation for party #{party_id}")
|
||||||
|
Rails.logger.info("Debug: should_generate? check starting")
|
||||||
|
|
||||||
party = Party.find(party_id)
|
party = Party.find(party_id)
|
||||||
|
Rails.logger.info("Party found: #{party.inspect}")
|
||||||
|
|
||||||
if party.preview_state == 'generated' &&
|
if party.preview_state == 'generated' &&
|
||||||
party.preview_generated_at &&
|
party.preview_generated_at &&
|
||||||
|
|
@ -23,9 +26,22 @@ class GeneratePartyPreviewJob < ApplicationJob
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
Rails.logger.info("Initializing PreviewService::Coordinator")
|
||||||
service = PreviewService::Coordinator.new(party)
|
service = PreviewService::Coordinator.new(party)
|
||||||
Rails.logger.info("Created PreviewService::Coordinator")
|
Rails.logger.info("Coordinator initialized")
|
||||||
|
|
||||||
|
Rails.logger.info("Checking should_generate?")
|
||||||
|
should_gen = service.send(:should_generate?)
|
||||||
|
Rails.logger.info("should_generate? returned: #{should_gen}")
|
||||||
|
|
||||||
|
if !should_gen
|
||||||
|
Rails.logger.info("Not generating preview because should_generate? returned false")
|
||||||
|
Rails.logger.info("Preview state: #{party.preview_state}")
|
||||||
|
Rails.logger.info("Generation in progress: #{service.send(:generation_in_progress?)}")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.logger.info("Starting generate_preview")
|
||||||
result = service.generate_preview
|
result = service.generate_preview
|
||||||
Rails.logger.info("Generate preview result: #{result}")
|
Rails.logger.info("Generate preview result: #{result}")
|
||||||
|
|
||||||
|
|
@ -38,7 +54,7 @@ class GeneratePartyPreviewJob < ApplicationJob
|
||||||
rescue => e
|
rescue => e
|
||||||
Rails.logger.error("Error generating preview for party #{party_id}: #{e.message}")
|
Rails.logger.error("Error generating preview for party #{party_id}: #{e.message}")
|
||||||
Rails.logger.error("Full error details:")
|
Rails.logger.error("Full error details:")
|
||||||
Rails.logger.error(e.full_message) # This will include the stack trace
|
Rails.logger.error(e.full_message)
|
||||||
notify_failure(party, e)
|
notify_failure(party, e)
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -106,10 +106,11 @@ class Party < ApplicationRecord
|
||||||
attr_accessor :favorited
|
attr_accessor :favorited
|
||||||
|
|
||||||
self.enum :preview_state, {
|
self.enum :preview_state, {
|
||||||
pending: 0, # Never generated
|
pending: 0,
|
||||||
queued: 1, # Generation job scheduled
|
queued: 1,
|
||||||
generated: 2, # Has preview image
|
in_progress: 2,
|
||||||
failed: 3 # Generation failed
|
generated: 3,
|
||||||
|
failed: 4
|
||||||
}
|
}
|
||||||
|
|
||||||
after_commit :schedule_preview_regeneration, if: :preview_relevant_changes?
|
after_commit :schedule_preview_regeneration, if: :preview_relevant_changes?
|
||||||
|
|
|
||||||
|
|
@ -15,26 +15,31 @@ module PreviewService
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_blank_canvas(width: PREVIEW_WIDTH, height: PREVIEW_HEIGHT, color: DEFAULT_BACKGROUND_COLOR)
|
def create_blank_canvas(width: PREVIEW_WIDTH, height: PREVIEW_HEIGHT, color: DEFAULT_BACKGROUND_COLOR)
|
||||||
Rails.logger.info("Checking ImageMagick installation...")
|
Rails.logger.info("Creating blank canvas #{width}x#{height}")
|
||||||
version = `convert -version`
|
|
||||||
Rails.logger.info("ImageMagick version: #{version}")
|
|
||||||
|
|
||||||
temp_file = Tempfile.new(%w[canvas .png])
|
temp_file = Tempfile.new(%w[canvas .png])
|
||||||
Rails.logger.info("Created temp file: #{temp_file.path}")
|
Rails.logger.info("Temp file created at: #{temp_file.path}")
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
Rails.logger.info("Checking ImageMagick setup...")
|
||||||
|
version = `which convert`
|
||||||
|
Rails.logger.info("ImageMagick convert path: #{version}")
|
||||||
|
|
||||||
|
Rails.logger.info("Executing convert command...")
|
||||||
MiniMagick::Tool::Convert.new do |convert|
|
MiniMagick::Tool::Convert.new do |convert|
|
||||||
convert.size "#{width}x#{height}"
|
convert.size "#{width}x#{height}"
|
||||||
convert << "xc:#{color}"
|
convert << "xc:#{color}"
|
||||||
convert << temp_file.path
|
convert << temp_file.path
|
||||||
end
|
end
|
||||||
Rails.logger.info("Canvas created successfully")
|
Rails.logger.info("Convert command completed successfully")
|
||||||
rescue => e
|
rescue => e
|
||||||
Rails.logger.error("Failed to create canvas: #{e.message}")
|
Rails.logger.error("Failed to create canvas with convert: #{e.class} - #{e.message}")
|
||||||
|
Rails.logger.error("PATH: #{ENV['PATH']}")
|
||||||
|
Rails.logger.error("LD_LIBRARY_PATH: #{ENV['LD_LIBRARY_PATH']}")
|
||||||
Rails.logger.error(e.backtrace.join("\n"))
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Rails.logger.info("Canvas created successfully at: #{temp_file.path}")
|
||||||
temp_file
|
temp_file
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -43,7 +48,12 @@ module PreviewService
|
||||||
font_color = options.fetch(:color, 'white')
|
font_color = options.fetch(:color, 'white')
|
||||||
|
|
||||||
# Load custom font for username, for later use
|
# Load custom font for username, for later use
|
||||||
@font_path ||= Rails.root.join('app', 'assets', 'fonts', 'Gk-Bd.otf').to_s
|
@font_path = Rails.root.join('app', 'assets', 'fonts', 'Gk-Bd.otf').to_s
|
||||||
|
Rails.logger.info("Using font path: #{@font_path}")
|
||||||
|
unless File.exist?(@font_path)
|
||||||
|
Rails.logger.error("Font file not found at: #{@font_path}")
|
||||||
|
raise "Font file not found"
|
||||||
|
end
|
||||||
|
|
||||||
# Measure party name text size
|
# Measure party name text size
|
||||||
text_metrics = measure_text(party_name, font_size)
|
text_metrics = measure_text(party_name, font_size)
|
||||||
|
|
|
||||||
|
|
@ -39,15 +39,38 @@ module PreviewService
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Rails.logger.info("Starting preview generation for party #{@party.id}")
|
Rails.logger.info("Starting preview generation for party #{@party.id}")
|
||||||
|
|
||||||
|
# Update state to in_progress
|
||||||
|
@party.update!(preview_state: :in_progress)
|
||||||
set_generation_in_progress
|
set_generation_in_progress
|
||||||
|
|
||||||
|
Rails.logger.info("Checking ImageMagick installation...")
|
||||||
|
begin
|
||||||
|
version = `convert -version`
|
||||||
|
Rails.logger.info("ImageMagick version: #{version}")
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Failed to get ImageMagick version: #{e.message}")
|
||||||
|
end
|
||||||
|
|
||||||
Rails.logger.info("Creating preview image...")
|
Rails.logger.info("Creating preview image...")
|
||||||
|
begin
|
||||||
image = create_preview_image
|
image = create_preview_image
|
||||||
Rails.logger.info("Preview image created successfully")
|
Rails.logger.info("Preview image created successfully")
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Failed to create preview image: #{e.class} - #{e.message}")
|
||||||
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
|
||||||
Rails.logger.info("Saving preview...")
|
Rails.logger.info("Saving preview...")
|
||||||
|
begin
|
||||||
save_preview(image)
|
save_preview(image)
|
||||||
Rails.logger.info("Preview saved successfully")
|
Rails.logger.info("Preview saved successfully")
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Failed to save preview: #{e.class} - #{e.message}")
|
||||||
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
|
||||||
Rails.logger.info("Updating party state...")
|
Rails.logger.info("Updating party state...")
|
||||||
@party.update!(
|
@party.update!(
|
||||||
|
|
@ -58,7 +81,7 @@ module PreviewService
|
||||||
|
|
||||||
true
|
true
|
||||||
rescue => e
|
rescue => e
|
||||||
Rails.logger.error("Failed to generate preview: #{e.class} - #{e.message}")
|
Rails.logger.error("Preview generation failed: #{e.class} - #{e.message}")
|
||||||
Rails.logger.error("Stack trace:")
|
Rails.logger.error("Stack trace:")
|
||||||
Rails.logger.error(e.backtrace.join("\n"))
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
handle_preview_generation_error(e)
|
handle_preview_generation_error(e)
|
||||||
|
|
@ -95,6 +118,38 @@ module PreviewService
|
||||||
Rails.logger.error("Failed to delete preview for party #{@party.id}: #{e.message}")
|
Rails.logger.error("Failed to delete preview for party #{@party.id}: #{e.message}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Determines if a new preview should be generated
|
||||||
|
#
|
||||||
|
# @return [Boolean] True if a new preview should be generated, false otherwise
|
||||||
|
def should_generate?
|
||||||
|
Rails.logger.info("Checking should_generate? conditions")
|
||||||
|
|
||||||
|
if generation_in_progress?
|
||||||
|
Rails.logger.info("Generation already in progress, returning false")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.logger.info("Preview state: #{@party.preview_state}")
|
||||||
|
# Add 'queued' to the list of valid states for generation
|
||||||
|
if @party.preview_state.in?(['pending', 'failed', 'queued'])
|
||||||
|
Rails.logger.info("Preview state is #{@party.preview_state}, returning true")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if @party.preview_state == 'generated'
|
||||||
|
if @party.preview_generated_at < PREVIEW_EXPIRY.ago
|
||||||
|
Rails.logger.info("Preview is older than expiry time, returning true")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
Rails.logger.info("Preview is recent, returning false")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.logger.info("No conditions met, returning false")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Sets up the appropriate storage system based on environment
|
# Sets up the appropriate storage system based on environment
|
||||||
|
|
@ -111,23 +166,45 @@ module PreviewService
|
||||||
# @return [MiniMagick::Image] The generated preview image
|
# @return [MiniMagick::Image] The generated preview image
|
||||||
def create_preview_image
|
def create_preview_image
|
||||||
Rails.logger.info("Creating blank canvas...")
|
Rails.logger.info("Creating blank canvas...")
|
||||||
|
begin
|
||||||
canvas = @canvas_service.create_blank_canvas
|
canvas = @canvas_service.create_blank_canvas
|
||||||
|
Rails.logger.info("Canvas created at: #{canvas.path}")
|
||||||
image = MiniMagick::Image.new(canvas.path)
|
image = MiniMagick::Image.new(canvas.path)
|
||||||
Rails.logger.info("Blank canvas created")
|
Rails.logger.info("MiniMagick image object created")
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Failed to create canvas: #{e.class} - #{e.message}")
|
||||||
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add more detailed logging for job icon handling
|
||||||
Rails.logger.info("Processing job icon...")
|
Rails.logger.info("Processing job icon...")
|
||||||
job_icon = nil
|
job_icon = nil
|
||||||
if @party.job.present?
|
if @party.job.present?
|
||||||
|
Rails.logger.info("Job present: #{@party.job.inspect}")
|
||||||
Rails.logger.info("Fetching job icon for job ID: #{@party.job.granblue_id}")
|
Rails.logger.info("Fetching job icon for job ID: #{@party.job.granblue_id}")
|
||||||
|
begin
|
||||||
job_icon = @image_fetcher.fetch_job_icon(@party.job.granblue_id)
|
job_icon = @image_fetcher.fetch_job_icon(@party.job.granblue_id)
|
||||||
Rails.logger.info("Job icon fetched successfully") if job_icon
|
Rails.logger.info("Job icon fetched successfully") if job_icon
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Failed to fetch job icon: #{e.class} - #{e.message}")
|
||||||
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
|
# Don't raise this error, just log it and continue without the job icon
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
Rails.logger.info("Adding party name and job icon...")
|
Rails.logger.info("Adding party name and job icon...")
|
||||||
text_result = @canvas_service.add_text(image, @party.name, job_icon: job_icon, user: @party.user)
|
text_result = @canvas_service.add_text(image, @party.name, job_icon: job_icon, user: @party.user)
|
||||||
image = text_result[:image]
|
image = text_result[:image]
|
||||||
Rails.logger.info("Party name and job icon added")
|
Rails.logger.info("Text and icon added successfully")
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Failed to add text/icon: #{e.class} - #{e.message}")
|
||||||
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
Rails.logger.info("Calculating grid layout...")
|
Rails.logger.info("Calculating grid layout...")
|
||||||
grid_layout = @grid_service.calculate_layout(
|
grid_layout = @grid_service.calculate_layout(
|
||||||
canvas_height: Canvas::PREVIEW_HEIGHT,
|
canvas_height: Canvas::PREVIEW_HEIGHT,
|
||||||
|
|
@ -136,8 +213,14 @@ module PreviewService
|
||||||
Rails.logger.info("Grid layout calculated")
|
Rails.logger.info("Grid layout calculated")
|
||||||
|
|
||||||
Rails.logger.info("Drawing weapons...")
|
Rails.logger.info("Drawing weapons...")
|
||||||
|
Rails.logger.info("Weapons count: #{@party.weapons.count}")
|
||||||
image = organize_and_draw_weapons(image, grid_layout)
|
image = organize_and_draw_weapons(image, grid_layout)
|
||||||
Rails.logger.info("Weapons drawn successfully")
|
Rails.logger.info("Weapons drawn successfully")
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Failed during weapons drawing: #{e.class} - #{e.message}")
|
||||||
|
Rails.logger.error(e.backtrace.join("\n"))
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
|
||||||
image
|
image
|
||||||
end
|
end
|
||||||
|
|
@ -310,25 +393,13 @@ module PreviewService
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Determines if a new preview should be generated
|
|
||||||
#
|
|
||||||
# @return [Boolean] True if a new preview should be generated, false otherwise
|
|
||||||
def should_generate?
|
|
||||||
return false if generation_in_progress?
|
|
||||||
return true if @party.preview_state.in?(['pending', 'failed'])
|
|
||||||
|
|
||||||
if @party.preview_state == 'generated'
|
|
||||||
return @party.preview_generated_at < PREVIEW_EXPIRY.ago
|
|
||||||
end
|
|
||||||
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Checks if a preview generation is currently in progress
|
# Checks if a preview generation is currently in progress
|
||||||
#
|
#
|
||||||
# @return [Boolean] True if a preview is being generated, false otherwise
|
# @return [Boolean] True if a preview is being generated, false otherwise
|
||||||
def generation_in_progress?
|
def generation_in_progress?
|
||||||
Rails.cache.exist?("party_preview_generating_#{@party.id}")
|
in_progress = Rails.cache.exist?("party_preview_generating_#{@party.id}")
|
||||||
|
Rails.logger.info("Cache key check for generation_in_progress: #{in_progress}")
|
||||||
|
in_progress
|
||||||
end
|
end
|
||||||
|
|
||||||
# Marks the preview generation as in progress
|
# Marks the preview generation as in progress
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddPreviewS3KeyToParties < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
add_column :parties, :preview_s3_key, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_01_18_135254) do
|
ActiveRecord::Schema[8.0].define(version: 2025_01_19_062554) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "btree_gin"
|
enable_extension "btree_gin"
|
||||||
enable_extension "pg_catalog.plpgsql"
|
enable_extension "pg_catalog.plpgsql"
|
||||||
|
|
@ -302,6 +302,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_18_135254) do
|
||||||
t.integer "visibility", default: 1, null: false
|
t.integer "visibility", default: 1, null: false
|
||||||
t.integer "preview_state", default: 0, null: false
|
t.integer "preview_state", default: 0, null: false
|
||||||
t.datetime "preview_generated_at"
|
t.datetime "preview_generated_at"
|
||||||
|
t.string "preview_s3_key"
|
||||||
t.index ["accessory_id"], name: "index_parties_on_accessory_id"
|
t.index ["accessory_id"], name: "index_parties_on_accessory_id"
|
||||||
t.index ["guidebook1_id"], name: "index_parties_on_guidebook1_id"
|
t.index ["guidebook1_id"], name: "index_parties_on_guidebook1_id"
|
||||||
t.index ["guidebook2_id"], name: "index_parties_on_guidebook2_id"
|
t.index ["guidebook2_id"], name: "index_parties_on_guidebook2_id"
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,8 @@ module PostDeployment
|
||||||
def rebuild_search_indices
|
def rebuild_search_indices
|
||||||
SearchIndexer.new(
|
SearchIndexer.new(
|
||||||
test_mode: @test_mode,
|
test_mode: @test_mode,
|
||||||
verbose: @verbose
|
verbose: @verbose,
|
||||||
|
new_records: @new_records
|
||||||
).rebuild_all
|
).rebuild_all
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ module PostDeployment
|
||||||
class SearchIndexer
|
class SearchIndexer
|
||||||
include LoggingHelper
|
include LoggingHelper
|
||||||
|
|
||||||
def initialize(test_mode:, verbose:)
|
def initialize(test_mode:, verbose:, new_records: {})
|
||||||
@test_mode = test_mode
|
@test_mode = test_mode
|
||||||
@verbose = verbose
|
@verbose = verbose
|
||||||
|
@new_records = new_records
|
||||||
end
|
end
|
||||||
|
|
||||||
def rebuild_all
|
def rebuild_all
|
||||||
|
|
@ -38,12 +39,22 @@ module PostDeployment
|
||||||
end
|
end
|
||||||
|
|
||||||
def rebuild_index_for(model)
|
def rebuild_index_for(model)
|
||||||
|
# Determine model type (lowercase, pluralized)
|
||||||
|
model_type = model.name.downcase.pluralize
|
||||||
|
|
||||||
|
# Check if there are new records for this model type
|
||||||
|
new_records = @new_records[model_type] || []
|
||||||
|
|
||||||
if @test_mode
|
if @test_mode
|
||||||
log_step "Would rebuild search index for #{model.name}"
|
log_step "Would rebuild search index for #{model.name}" if new_records.any?
|
||||||
else
|
else
|
||||||
|
if new_records.any?
|
||||||
log_verbose "• #{model.name}... "
|
log_verbose "• #{model.name}... "
|
||||||
PgSearch::Multisearch.rebuild(model)
|
PgSearch::Multisearch.rebuild(model)
|
||||||
log_verbose "✅ done!\n"
|
log_verbose "✅ done! (#{new_records.size} new records)\n"
|
||||||
|
else
|
||||||
|
log_step "Skipping #{model.name} - no new records" if @verbose
|
||||||
|
end
|
||||||
end
|
end
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
log_error("Failed to rebuild index for #{model.name}: #{e.message}")
|
log_error("Failed to rebuild index for #{model.name}: #{e.message}")
|
||||||
|
|
|
||||||
17
railway.toml
17
railway.toml
|
|
@ -1,9 +1,20 @@
|
||||||
[phases.setup]
|
[phases.setup]
|
||||||
nixPkgs = [
|
nixPkgs = [
|
||||||
"imagemagick",
|
"imagemagick",
|
||||||
"ghostscript", # For PDF/PS operations
|
# For PDF/PS operations
|
||||||
"pkgconfig", # For gem compilation
|
"ghostscript",
|
||||||
"libmagickwand" # ImageMagick C library
|
# For gem compilation
|
||||||
|
"pkgconfig",
|
||||||
|
# ImageMagick C library
|
||||||
|
"libmagickwand",
|
||||||
|
# Font configuration
|
||||||
|
"fontconfig",
|
||||||
|
# A good fallback font
|
||||||
|
"dejavu_fonts",
|
||||||
|
# Arial-compatible fonts
|
||||||
|
"liberation_ttf",
|
||||||
|
# Font rendering
|
||||||
|
"freetype"
|
||||||
]
|
]
|
||||||
|
|
||||||
[phases.install]
|
[phases.install]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue