Updates to Canvas and Coordinator
This commit is contained in:
parent
e9d9940d96
commit
8207b3a09a
2 changed files with 125 additions and 62 deletions
|
|
@ -44,11 +44,25 @@ module PreviewService
|
|||
end
|
||||
|
||||
def add_text(image, party_name, job_icon: nil, user: nil, **options)
|
||||
party_name = party_name.to_s.strip
|
||||
party_name = 'Untitled' if party_name.empty?
|
||||
|
||||
font_size = options.fetch(:size, '32')
|
||||
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
|
||||
# Try multiple font locations
|
||||
font_locations = [
|
||||
Rails.root.join('app', 'assets', 'fonts', 'Gk-Bd.otf').to_s,
|
||||
Rails.root.join('public', 'assets', 'fonts', 'Gk-Bd.otf').to_s
|
||||
]
|
||||
|
||||
@font_path = font_locations.find { |path| File.exist?(path) }
|
||||
|
||||
unless @font_path
|
||||
Rails.logger.error("Font file not found in any location: #{font_locations.join(', ')}")
|
||||
raise "Font file not found"
|
||||
end
|
||||
|
||||
Rails.logger.info("Using font path: #{@font_path}")
|
||||
unless File.exist?(@font_path)
|
||||
Rails.logger.error("Font file not found at: #{@font_path}")
|
||||
|
|
@ -94,16 +108,18 @@ module PreviewService
|
|||
end
|
||||
|
||||
def draw_party_name(image, party_name, text_metrics, job_icon, font_color, font_size)
|
||||
# Determine x position based on presence of job_icon
|
||||
text_x = job_icon ? PADDING + 64 + 16 : PADDING
|
||||
text_y = PADDING + text_metrics[:height]
|
||||
|
||||
image.combine_options do |c|
|
||||
c.font @font_path
|
||||
c.gravity 'NorthWest'
|
||||
c.fill font_color
|
||||
c.font @font_path
|
||||
c.pointsize font_size
|
||||
c.draw "text #{text_x},#{text_y} '#{party_name}'"
|
||||
# Escape quotes and use pango markup for better text handling
|
||||
c.annotate "0x0+#{text_x}+#{text_y}", party_name.gsub('"', '\"')
|
||||
end
|
||||
|
||||
image
|
||||
end
|
||||
|
||||
|
|
@ -154,7 +170,13 @@ module PreviewService
|
|||
image
|
||||
end
|
||||
|
||||
def measure_text(text, font_size, font: 'Arial')
|
||||
def measure_text(text, font_size, font: @font_path)
|
||||
# Ensure text is not empty and is properly escaped
|
||||
text = text.to_s.strip
|
||||
text = 'Untitled' if text.empty?
|
||||
|
||||
# Escape text for shell command
|
||||
escaped_text = text.gsub(/'/, "'\\\\''")
|
||||
|
||||
# Create a temporary file for the text measurement
|
||||
temp_file = Tempfile.new(['text_measure', '.png'])
|
||||
|
|
@ -167,7 +189,7 @@ module PreviewService
|
|||
'-fill', 'black',
|
||||
'-font', font,
|
||||
'-pointsize', font_size.to_s,
|
||||
"label:#{text}",
|
||||
"label:'#{escaped_text}'", # Quote the text
|
||||
temp_file.path
|
||||
]
|
||||
|
||||
|
|
@ -181,15 +203,15 @@ module PreviewService
|
|||
height: image.height,
|
||||
width: image.width
|
||||
}
|
||||
rescue => e
|
||||
Rails.logger.error "Text measurement error: #{e.message}"
|
||||
# Fallback dimensions
|
||||
{ height: 50, width: 200 }
|
||||
ensure
|
||||
# Close and unlink the temporary file
|
||||
temp_file.close
|
||||
temp_file.unlink
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "Text measurement error: #{e.message}"
|
||||
# Fallback dimensions
|
||||
{ height: 50, width: 200 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ module PreviewService
|
|||
GENERATION_TIMEOUT = 5.minutes
|
||||
LOCAL_STORAGE_PATH = Rails.root.join('storage', 'party-previews')
|
||||
|
||||
# Public Interface - Core Operations
|
||||
|
||||
# Initialize the party preview service
|
||||
#
|
||||
# @param party [Party] The party to generate a preview for
|
||||
|
|
@ -103,6 +105,8 @@ module PreviewService
|
|||
end
|
||||
|
||||
# Deletes the existing preview image for the party
|
||||
#
|
||||
# @return [void]
|
||||
def delete_preview
|
||||
if Rails.env.production?
|
||||
delete_s3_preview
|
||||
|
|
@ -118,6 +122,8 @@ module PreviewService
|
|||
Rails.logger.error("Failed to delete preview for party #{@party.id}: #{e.message}")
|
||||
end
|
||||
|
||||
# State Management - Public
|
||||
|
||||
# Determines if a new preview should be generated
|
||||
#
|
||||
# @return [Boolean] True if a new preview should be generated, false otherwise
|
||||
|
|
@ -150,15 +156,43 @@ module PreviewService
|
|||
false
|
||||
end
|
||||
|
||||
private
|
||||
# Checks if a preview generation is currently in progress
|
||||
#
|
||||
# @return [Boolean] True if a preview is being generated, false otherwise
|
||||
def generation_in_progress?
|
||||
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
|
||||
|
||||
# Sets up the appropriate storage system based on environment
|
||||
def setup_storage
|
||||
# Always initialize AWS service for potential image fetching
|
||||
@aws_service = AwsService.new
|
||||
# Retrieves the S3 object for the party's preview image
|
||||
#
|
||||
# @return [Aws::S3::Types::GetObjectOutput] S3 object containing the preview image
|
||||
# @raise [Aws::S3::Errors::NoSuchKey] If the preview image doesn't exist in S3
|
||||
# @raise [Aws::S3::Errors::NoSuchBucket] If the configured bucket doesn't exist
|
||||
def get_s3_object
|
||||
@aws_service.s3_client.get_object(
|
||||
bucket: @aws_service.bucket,
|
||||
key: preview_key
|
||||
)
|
||||
end
|
||||
|
||||
# Create local storage paths in development
|
||||
FileUtils.mkdir_p(LOCAL_STORAGE_PATH) unless Dir.exist?(LOCAL_STORAGE_PATH.to_s)
|
||||
# Schedules a background job to generate the preview
|
||||
#
|
||||
# @return [void]
|
||||
def schedule_generation
|
||||
GeneratePartyPreviewJob
|
||||
.set(wait: 30.seconds)
|
||||
.perform_later(@party.id)
|
||||
|
||||
@party.update!(preview_state: :queued)
|
||||
end
|
||||
|
||||
# Returns the full path for storing preview images locally
|
||||
#
|
||||
# @return [Pathname] Full path where the preview image should be stored
|
||||
def local_preview_path
|
||||
LOCAL_STORAGE_PATH.join(preview_filename)
|
||||
end
|
||||
|
||||
# Creates the preview image for the party
|
||||
|
|
@ -225,6 +259,21 @@ module PreviewService
|
|||
image
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Sets up the appropriate storage system based on environment
|
||||
#
|
||||
# @return [void]
|
||||
def setup_storage
|
||||
# Always initialize AWS service for potential image fetching
|
||||
@aws_service = AwsService.new
|
||||
|
||||
# Create local storage paths in development
|
||||
FileUtils.mkdir_p(LOCAL_STORAGE_PATH) unless Dir.exist?(LOCAL_STORAGE_PATH.to_s)
|
||||
end
|
||||
|
||||
# Image Generation Pipeline
|
||||
|
||||
# Adds the job icon to the preview image
|
||||
#
|
||||
# @param image [MiniMagick::Image] The base image
|
||||
|
|
@ -241,6 +290,7 @@ module PreviewService
|
|||
# Organizes and draws weapons on the preview image
|
||||
#
|
||||
# @param image [MiniMagick::Image] The base image
|
||||
# @param grid_layout [Hash] The layout configuration for the grid
|
||||
# @return [MiniMagick::Image] The updated image with weapons
|
||||
def organize_and_draw_weapons(image, grid_layout)
|
||||
mainhand_weapon = @party.weapons.find(&:mainhand)
|
||||
|
|
@ -277,9 +327,12 @@ module PreviewService
|
|||
end
|
||||
end
|
||||
|
||||
# Storage Operations
|
||||
|
||||
# Saves the preview image to the appropriate storage system
|
||||
#
|
||||
# @param image [MiniMagick::Image] The image to save
|
||||
# @return [void]
|
||||
def save_preview(image)
|
||||
if Rails.env.production?
|
||||
upload_to_s3(image)
|
||||
|
|
@ -291,14 +344,14 @@ module PreviewService
|
|||
# Uploads the preview image to S3
|
||||
#
|
||||
# @param image [MiniMagick::Image] The image to upload
|
||||
# @return [void]
|
||||
def upload_to_s3(image)
|
||||
temp_file = Tempfile.new(['preview', '.png'])
|
||||
temp_file = Tempfile.new(%w[preview .png])
|
||||
begin
|
||||
image.write(temp_file.path)
|
||||
|
||||
# Use timestamped filename similar to local storage
|
||||
timestamp = Time.current.strftime('%Y%m%d%H%M%S')
|
||||
key = "#{PREVIEW_FOLDER}/#{@party.shortcode}_#{timestamp}.png"
|
||||
# Use fixed key without timestamp
|
||||
key = "#{PREVIEW_FOLDER}/#{@party.shortcode}.png"
|
||||
|
||||
File.open(temp_file.path, 'rb') do |file|
|
||||
@aws_service.s3_client.put_object(
|
||||
|
|
@ -310,7 +363,6 @@ module PreviewService
|
|||
)
|
||||
end
|
||||
|
||||
# Optionally, store this key on the party record if needed for retrieval
|
||||
@party.update!(preview_s3_key: key)
|
||||
ensure
|
||||
temp_file.close
|
||||
|
|
@ -321,29 +373,18 @@ module PreviewService
|
|||
# Saves the preview image to local storage
|
||||
#
|
||||
# @param image [MiniMagick::Image] The image to save
|
||||
# @return [void]
|
||||
def save_to_local_storage(image)
|
||||
# Remove any existing previews for this party
|
||||
Dir.glob(LOCAL_STORAGE_PATH.join("#{@party.shortcode}_*.png").to_s).each do |file|
|
||||
File.delete(file)
|
||||
end
|
||||
|
||||
# Save new version
|
||||
image.write(local_preview_path)
|
||||
end
|
||||
|
||||
# Generates a timestamped filename for the preview image
|
||||
#
|
||||
# @return [String] Filename in format "shortcode_YYYYMMDDHHMMSS.png"
|
||||
def preview_filename
|
||||
timestamp = Time.current.strftime('%Y%m%d%H%M%S')
|
||||
"#{@party.shortcode}_#{timestamp}.png"
|
||||
end
|
||||
# Path & URL Generation
|
||||
|
||||
# Returns the full path for storing preview images locally
|
||||
# Generates a filename for the preview image
|
||||
#
|
||||
# @return [Pathname] Full path where the preview image should be stored
|
||||
def local_preview_path
|
||||
LOCAL_STORAGE_PATH.join(preview_filename)
|
||||
# @return [String] Filename for the preview image
|
||||
def preview_filename
|
||||
"#{@party.shortcode}.png"
|
||||
end
|
||||
|
||||
# Returns the URL for accessing locally stored preview images
|
||||
|
|
@ -364,6 +405,8 @@ module PreviewService
|
|||
"#{PREVIEW_FOLDER}/#{@party.shortcode}.png"
|
||||
end
|
||||
|
||||
# Preview State Management
|
||||
|
||||
# Checks if a preview image exists for the party
|
||||
#
|
||||
# @return [Boolean] True if a preview exists, false otherwise
|
||||
|
|
@ -371,10 +414,9 @@ module PreviewService
|
|||
return false unless @party.preview_state == 'generated'
|
||||
|
||||
if Rails.env.production?
|
||||
@aws_service.s3_client.head_object(bucket: S3_BUCKET, key: preview_key)
|
||||
true
|
||||
@aws_service.file_exists?(preview_key)
|
||||
else
|
||||
!Dir.glob(LOCAL_STORAGE_PATH.join("#{@party.shortcode}_*.png").to_s).empty?
|
||||
!Dir.glob(LOCAL_STORAGE_PATH.join("#{@party.shortcode}.png").to_s).empty?
|
||||
end
|
||||
rescue Aws::S3::Errors::NotFound
|
||||
false
|
||||
|
|
@ -387,22 +429,15 @@ module PreviewService
|
|||
signer = Aws::S3::Presigner.new(client: @aws_service.s3_client)
|
||||
signer.presigned_url(
|
||||
:get_object,
|
||||
bucket: S3_BUCKET,
|
||||
bucket: @aws_service.bucket,
|
||||
key: preview_key,
|
||||
expires_in: 1.hour
|
||||
expires_in: 1.hour.to_i
|
||||
)
|
||||
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?
|
||||
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
|
||||
#
|
||||
# @return [void]
|
||||
def set_generation_in_progress
|
||||
Rails.cache.write(
|
||||
"party_preview_generating_#{@party.id}",
|
||||
|
|
@ -412,18 +447,15 @@ module PreviewService
|
|||
end
|
||||
|
||||
# Clears the in-progress flag for preview generation
|
||||
#
|
||||
# @return [void]
|
||||
def clear_generation_in_progress
|
||||
Rails.cache.delete("party_preview_generating_#{@party.id}")
|
||||
end
|
||||
|
||||
# Schedules a background job to generate the preview
|
||||
def schedule_generation
|
||||
GeneratePartyPreviewJob
|
||||
.set(wait: 30.seconds)
|
||||
.perform_later(@party.id)
|
||||
# Job Scheduling
|
||||
|
||||
@party.update!(preview_state: :queued)
|
||||
end
|
||||
# URL Generation
|
||||
|
||||
# Provides a default preview URL based on party attributes
|
||||
#
|
||||
|
|
@ -436,24 +468,33 @@ module PreviewService
|
|||
end
|
||||
end
|
||||
|
||||
# Cleanup Operations
|
||||
|
||||
# Deletes the preview from S3
|
||||
#
|
||||
# @return [void]
|
||||
def delete_s3_preview
|
||||
@aws_service.s3_client.delete_object(
|
||||
bucket: S3_BUCKET,
|
||||
bucket: @aws_service.bucket,
|
||||
key: preview_key
|
||||
)
|
||||
end
|
||||
|
||||
# Deletes local preview files
|
||||
#
|
||||
# @return [void]
|
||||
def delete_local_previews
|
||||
Dir.glob(LOCAL_STORAGE_PATH.join("#{@party.shortcode}_*.png").to_s).each do |file|
|
||||
File.delete(file)
|
||||
end
|
||||
end
|
||||
|
||||
# Error Handling
|
||||
|
||||
# Handles errors during preview generation
|
||||
#
|
||||
# @param error [Exception] The error that occurred
|
||||
# @return [void]
|
||||
def handle_preview_generation_error(error)
|
||||
Rails.logger.error("Preview generation failed for party #{@party.id}")
|
||||
Rails.logger.error("Error: #{error.class} - #{error.message}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue