add artifact image download service and job
This commit is contained in:
parent
e5d80bde23
commit
70e6d50371
2 changed files with 153 additions and 0 deletions
87
app/jobs/download_artifact_images_job.rb
Normal file
87
app/jobs/download_artifact_images_job.rb
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Background job for downloading artifact images from Granblue servers to S3.
|
||||||
|
# Stores progress in Redis for status polling.
|
||||||
|
#
|
||||||
|
# @example Enqueue a download job
|
||||||
|
# job = DownloadArtifactImagesJob.perform_later(artifact.id)
|
||||||
|
# # Poll status with: DownloadArtifactImagesJob.status(artifact.id)
|
||||||
|
class DownloadArtifactImagesJob < ApplicationJob
|
||||||
|
queue_as :downloads
|
||||||
|
|
||||||
|
retry_on StandardError, wait: :exponentially_longer, attempts: 3
|
||||||
|
|
||||||
|
discard_on ActiveRecord::RecordNotFound do |job, _error|
|
||||||
|
artifact_id = job.arguments.first
|
||||||
|
Rails.logger.error "[DownloadArtifactImages] Artifact #{artifact_id} not found"
|
||||||
|
update_status(artifact_id, 'failed', error: 'Artifact not found')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Status keys for Redis storage
|
||||||
|
REDIS_KEY_PREFIX = 'artifact_image_download'
|
||||||
|
STATUS_TTL = 1.hour.to_i
|
||||||
|
|
||||||
|
class << self
|
||||||
|
# Get the current status of a download job for an artifact
|
||||||
|
#
|
||||||
|
# @param artifact_id [String] UUID of the artifact
|
||||||
|
# @return [Hash] Status hash with :status, :progress, :images_downloaded, :images_total, :error
|
||||||
|
def status(artifact_id)
|
||||||
|
data = redis.get(redis_key(artifact_id))
|
||||||
|
return { status: 'not_found' } unless data
|
||||||
|
|
||||||
|
JSON.parse(data, symbolize_names: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def redis_key(artifact_id)
|
||||||
|
"#{REDIS_KEY_PREFIX}:#{artifact_id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def redis
|
||||||
|
@redis ||= Redis.new(url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_status(artifact_id, status, **attrs)
|
||||||
|
data = { status: status, updated_at: Time.current.iso8601 }.merge(attrs)
|
||||||
|
redis.setex(redis_key(artifact_id), STATUS_TTL, data.to_json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(artifact_id, force: false, size: 'all')
|
||||||
|
Rails.logger.info "[DownloadArtifactImages] Starting download for artifact #{artifact_id}"
|
||||||
|
|
||||||
|
artifact = Artifact.find(artifact_id)
|
||||||
|
update_status(artifact_id, 'processing', progress: 0, images_downloaded: 0)
|
||||||
|
|
||||||
|
service = ArtifactImageDownloadService.new(
|
||||||
|
artifact,
|
||||||
|
force: force,
|
||||||
|
size: size,
|
||||||
|
storage: :s3
|
||||||
|
)
|
||||||
|
|
||||||
|
result = service.download
|
||||||
|
|
||||||
|
if result.success?
|
||||||
|
Rails.logger.info "[DownloadArtifactImages] Completed for artifact #{artifact_id}"
|
||||||
|
update_status(
|
||||||
|
artifact_id,
|
||||||
|
'completed',
|
||||||
|
progress: 100,
|
||||||
|
images_downloaded: result.total,
|
||||||
|
images_total: result.total,
|
||||||
|
images: result.images
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Rails.logger.error "[DownloadArtifactImages] Failed for artifact #{artifact_id}: #{result.error}"
|
||||||
|
update_status(artifact_id, 'failed', error: result.error)
|
||||||
|
raise StandardError, result.error # Trigger retry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def update_status(artifact_id, status, **attrs)
|
||||||
|
self.class.update_status(artifact_id, status, **attrs)
|
||||||
|
end
|
||||||
|
end
|
||||||
66
app/services/artifact_image_download_service.rb
Normal file
66
app/services/artifact_image_download_service.rb
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Service wrapper for downloading artifact images from Granblue servers to S3.
|
||||||
|
# Uses the existing ArtifactDownloader but provides a cleaner interface for controllers.
|
||||||
|
#
|
||||||
|
# @example Download images for an artifact
|
||||||
|
# service = ArtifactImageDownloadService.new(artifact)
|
||||||
|
# result = service.download
|
||||||
|
# if result.success?
|
||||||
|
# puts result.images
|
||||||
|
# else
|
||||||
|
# puts result.error
|
||||||
|
# end
|
||||||
|
class ArtifactImageDownloadService
|
||||||
|
Result = Struct.new(:success?, :images, :error, :total, keyword_init: true)
|
||||||
|
|
||||||
|
def initialize(artifact, options = {})
|
||||||
|
@artifact = artifact
|
||||||
|
@force = options[:force] || false
|
||||||
|
@size = options[:size] || 'all'
|
||||||
|
@storage = options[:storage] || :s3
|
||||||
|
end
|
||||||
|
|
||||||
|
# Downloads images for the artifact
|
||||||
|
#
|
||||||
|
# @return [Result] Struct with success status, images manifest, and any errors
|
||||||
|
def download
|
||||||
|
downloader = Granblue::Downloaders::ArtifactDownloader.new(
|
||||||
|
@artifact.granblue_id,
|
||||||
|
storage: @storage,
|
||||||
|
force: @force,
|
||||||
|
verbose: Rails.env.development?
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_size = @size == 'all' ? nil : @size
|
||||||
|
downloader.download(selected_size)
|
||||||
|
|
||||||
|
manifest = build_image_manifest
|
||||||
|
|
||||||
|
Result.new(
|
||||||
|
success?: true,
|
||||||
|
images: manifest,
|
||||||
|
total: count_total_images(manifest)
|
||||||
|
)
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error "[ArtifactImageDownload] Failed for #{@artifact.granblue_id}: #{e.message}"
|
||||||
|
Result.new(
|
||||||
|
success?: false,
|
||||||
|
error: e.message
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_image_manifest
|
||||||
|
sizes = Granblue::Downloaders::ArtifactDownloader::SIZES
|
||||||
|
|
||||||
|
sizes.each_with_object({}) do |size, manifest|
|
||||||
|
manifest[size] = ["#{@artifact.granblue_id}.jpg"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def count_total_images(manifest)
|
||||||
|
manifest.values.sum(&:size)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue