diff --git a/app/jobs/generate_party_preview_job.rb b/app/jobs/generate_party_preview_job.rb index b73a7f4..4453c83 100644 --- a/app/jobs/generate_party_preview_job.rb +++ b/app/jobs/generate_party_preview_job.rb @@ -13,7 +13,10 @@ class GeneratePartyPreviewJob < ApplicationJob def perform(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) + Rails.logger.info("Party found: #{party.inspect}") if party.preview_state == 'generated' && party.preview_generated_at && @@ -23,9 +26,22 @@ class GeneratePartyPreviewJob < ApplicationJob end begin + Rails.logger.info("Initializing PreviewService::Coordinator") 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 Rails.logger.info("Generate preview result: #{result}") @@ -38,7 +54,7 @@ class GeneratePartyPreviewJob < ApplicationJob rescue => e Rails.logger.error("Error generating preview for party #{party_id}: #{e.message}") 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) raise end diff --git a/app/models/party.rb b/app/models/party.rb index f0944e7..f1bebc2 100644 --- a/app/models/party.rb +++ b/app/models/party.rb @@ -106,10 +106,11 @@ class Party < ApplicationRecord attr_accessor :favorited self.enum :preview_state, { - pending: 0, # Never generated - queued: 1, # Generation job scheduled - generated: 2, # Has preview image - failed: 3 # Generation failed + pending: 0, + queued: 1, + in_progress: 2, + generated: 3, + failed: 4 } after_commit :schedule_preview_regeneration, if: :preview_relevant_changes? diff --git a/app/services/preview_service/canvas.rb b/app/services/preview_service/canvas.rb index 1477ec0..a632635 100644 --- a/app/services/preview_service/canvas.rb +++ b/app/services/preview_service/canvas.rb @@ -15,26 +15,31 @@ module PreviewService end def create_blank_canvas(width: PREVIEW_WIDTH, height: PREVIEW_HEIGHT, color: DEFAULT_BACKGROUND_COLOR) - Rails.logger.info("Checking ImageMagick installation...") - version = `convert -version` - Rails.logger.info("ImageMagick version: #{version}") - + Rails.logger.info("Creating blank canvas #{width}x#{height}") 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 + 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| convert.size "#{width}x#{height}" convert << "xc:#{color}" convert << temp_file.path end - Rails.logger.info("Canvas created successfully") + Rails.logger.info("Convert command completed successfully") 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")) raise end + Rails.logger.info("Canvas created successfully at: #{temp_file.path}") temp_file end @@ -43,7 +48,12 @@ module PreviewService font_color = options.fetch(:color, 'white') # 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 text_metrics = measure_text(party_name, font_size) diff --git a/app/services/preview_service/coordinator.rb b/app/services/preview_service/coordinator.rb index 2fad8a6..90409f7 100644 --- a/app/services/preview_service/coordinator.rb +++ b/app/services/preview_service/coordinator.rb @@ -39,15 +39,38 @@ module PreviewService begin 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 + 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...") - image = create_preview_image - Rails.logger.info("Preview image created successfully") + begin + image = create_preview_image + 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...") - save_preview(image) - Rails.logger.info("Preview saved successfully") + begin + save_preview(image) + 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...") @party.update!( @@ -58,7 +81,7 @@ module PreviewService true 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(e.backtrace.join("\n")) handle_preview_generation_error(e) @@ -95,6 +118,38 @@ module PreviewService Rails.logger.error("Failed to delete preview for party #{@party.id}: #{e.message}") 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 # Sets up the appropriate storage system based on environment @@ -111,33 +166,61 @@ module PreviewService # @return [MiniMagick::Image] The generated preview image def create_preview_image Rails.logger.info("Creating blank canvas...") - canvas = @canvas_service.create_blank_canvas - image = MiniMagick::Image.new(canvas.path) - Rails.logger.info("Blank canvas created") + begin + canvas = @canvas_service.create_blank_canvas + Rails.logger.info("Canvas created at: #{canvas.path}") + image = MiniMagick::Image.new(canvas.path) + 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...") job_icon = nil 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}") - job_icon = @image_fetcher.fetch_job_icon(@party.job.granblue_id) - Rails.logger.info("Job icon fetched successfully") if job_icon + begin + job_icon = @image_fetcher.fetch_job_icon(@party.job.granblue_id) + 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 - 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) - image = text_result[:image] - Rails.logger.info("Party name and job icon added") + begin + 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) + image = text_result[:image] + 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 - Rails.logger.info("Calculating grid layout...") - grid_layout = @grid_service.calculate_layout( - canvas_height: Canvas::PREVIEW_HEIGHT, - title_bottom_y: text_result[:text_bottom_y] - ) - Rails.logger.info("Grid layout calculated") + begin + Rails.logger.info("Calculating grid layout...") + grid_layout = @grid_service.calculate_layout( + canvas_height: Canvas::PREVIEW_HEIGHT, + title_bottom_y: text_result[:text_bottom_y] + ) + Rails.logger.info("Grid layout calculated") - Rails.logger.info("Drawing weapons...") - image = organize_and_draw_weapons(image, grid_layout) - Rails.logger.info("Weapons drawn successfully") + Rails.logger.info("Drawing weapons...") + Rails.logger.info("Weapons count: #{@party.weapons.count}") + image = organize_and_draw_weapons(image, grid_layout) + 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 end @@ -310,25 +393,13 @@ module PreviewService ) 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 # # @return [Boolean] True if a preview is being generated, false otherwise 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 # Marks the preview generation as in progress diff --git a/db/migrate/20250119062554_add_preview_s3_key_to_parties.rb b/db/migrate/20250119062554_add_preview_s3_key_to_parties.rb new file mode 100644 index 0000000..b698ef4 --- /dev/null +++ b/db/migrate/20250119062554_add_preview_s3_key_to_parties.rb @@ -0,0 +1,5 @@ +class AddPreviewS3KeyToParties < ActiveRecord::Migration[8.0] + def change + add_column :parties, :preview_s3_key, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index ffaeb01..b07f427 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # 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 enable_extension "btree_gin" 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 "preview_state", default: 0, null: false t.datetime "preview_generated_at" + t.string "preview_s3_key" t.index ["accessory_id"], name: "index_parties_on_accessory_id" t.index ["guidebook1_id"], name: "index_parties_on_guidebook1_id" t.index ["guidebook2_id"], name: "index_parties_on_guidebook2_id" diff --git a/lib/post_deployment/manager.rb b/lib/post_deployment/manager.rb index 64a24f8..a997a9e 100644 --- a/lib/post_deployment/manager.rb +++ b/lib/post_deployment/manager.rb @@ -85,7 +85,8 @@ module PostDeployment def rebuild_search_indices SearchIndexer.new( test_mode: @test_mode, - verbose: @verbose + verbose: @verbose, + new_records: @new_records ).rebuild_all end diff --git a/lib/post_deployment/search_indexer.rb b/lib/post_deployment/search_indexer.rb index 8fee55d..786bf46 100644 --- a/lib/post_deployment/search_indexer.rb +++ b/lib/post_deployment/search_indexer.rb @@ -6,9 +6,10 @@ module PostDeployment class SearchIndexer include LoggingHelper - def initialize(test_mode:, verbose:) + def initialize(test_mode:, verbose:, new_records: {}) @test_mode = test_mode @verbose = verbose + @new_records = new_records end def rebuild_all @@ -38,12 +39,22 @@ module PostDeployment end 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 - log_step "Would rebuild search index for #{model.name}" + log_step "Would rebuild search index for #{model.name}" if new_records.any? else - log_verbose "• #{model.name}... " - PgSearch::Multisearch.rebuild(model) - log_verbose "✅ done!\n" + if new_records.any? + log_verbose "• #{model.name}... " + PgSearch::Multisearch.rebuild(model) + log_verbose "✅ done! (#{new_records.size} new records)\n" + else + log_step "Skipping #{model.name} - no new records" if @verbose + end end rescue StandardError => e log_error("Failed to rebuild index for #{model.name}: #{e.message}") diff --git a/railway.toml b/railway.toml index 2cdf95e..ee5c10a 100644 --- a/railway.toml +++ b/railway.toml @@ -1,9 +1,20 @@ [phases.setup] nixPkgs = [ "imagemagick", - "ghostscript", # For PDF/PS operations - "pkgconfig", # For gem compilation - "libmagickwand" # ImageMagick C library + # For PDF/PS operations + "ghostscript", + # 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]