Packages

phoenix_kit

1.7.177
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules storage workers process_file_job.ex
Raw

lib/modules/storage/workers/process_file_job.ex

defmodule PhoenixKit.Modules.Storage.ProcessFileJob do
@moduledoc """
Oban job for background processing of uploaded files.
This job handles:
- Generating file variants (thumbnails, resizes)
- Extracting metadata (dimensions, duration)
- Updating file status
"""
use Oban.Worker, queue: :file_processing, max_attempts: 3
require Logger
alias PhoenixKit.Modules.Storage
alias PhoenixKit.Modules.Storage.ImageProcessor
alias PhoenixKit.Modules.Storage.PdfProcessor
alias PhoenixKit.Modules.Storage.VariantGenerator
@doc """
Process a file and generate variants.
"""
@impl Oban.Worker
def perform(%Oban.Job{
args: %{"file_uuid" => file_uuid, "filename" => filename} = _args
}) do
Logger.info("ProcessFileJob: EXECUTING for file_uuid=#{file_uuid}, filename=#{filename}")
file = Storage.get_file(file_uuid)
if is_nil(file) do
Logger.error("ProcessFileJob: File not found for file_uuid=#{file_uuid}")
{:error, :file_not_found}
else
Logger.info(
"ProcessFileJob: Starting processing for file_uuid=#{file_uuid}, type=#{file.file_type}"
)
case process_file(file) do
{:ok, variants} ->
Logger.info(
"ProcessFileJob: Successfully processed file_uuid=#{file_uuid}, generated=#{length(variants)} variants"
)
:ok
{:error, reason} ->
Logger.error(
"ProcessFileJob: Failed to process file_uuid=#{file_uuid}, error=#{inspect(reason)}"
)
{:error, reason}
end
end
end
@impl Oban.Worker
def timeout(_job), do: :timer.minutes(5)
defp process_file(%PhoenixKit.Modules.Storage.File{} = file) do
case file.file_type do
"image" ->
process_image(file)
"video" ->
process_video(file)
"document" ->
process_document(file)
_ ->
Logger.info("ProcessFileJob: Skipping processing for file type=#{file.file_type}")
{:ok, []}
end
end
defp process_image(file) do
Logger.info("ProcessFileJob: process_image/1 called for file_uuid=#{file.uuid}")
with {:ok, temp_path} <- retrieve_and_log_file(file.uuid),
{:ok, metadata} <- extract_and_log_image_metadata(temp_path),
:ok <- update_and_log_metadata(file, metadata),
:ok <- log_dimensions_info(),
{:ok, variants} <- generate_and_log_variants(file) do
File.rm(temp_path)
{:ok, variants}
else
{:error, reason} = error ->
Logger.error("ProcessFileJob: Failed to process image: #{inspect(reason)}")
error
end
end
defp retrieve_and_log_file(file_uuid) do
case Storage.retrieve_file(file_uuid) do
{:ok, temp_path, _file} ->
Logger.info("ProcessFileJob: Retrieved file to temp_path=#{temp_path}")
{:ok, temp_path}
error ->
error
end
end
defp extract_and_log_image_metadata(temp_path) do
# extract_image_metadata always returns {:ok, metadata}
{:ok, metadata} = extract_image_metadata(temp_path)
Logger.info("ProcessFileJob: Extracted metadata=#{inspect(metadata)}")
{:ok, metadata}
end
defp update_and_log_metadata(file, metadata) do
case update_file_with_metadata(file, metadata) do
:ok = success ->
Logger.info("ProcessFileJob: Updated file with metadata")
success
error ->
error
end
end
defp log_dimensions_info do
dimensions = Storage.list_dimensions_for_type("image")
Logger.info("ProcessFileJob: Found #{length(dimensions)} dimensions for images")
:ok
end
defp generate_and_log_variants(file) do
case VariantGenerator.generate_variants(file) do
{:ok, variants} = success ->
Logger.info("ProcessFileJob: Generated #{length(variants)} variants successfully")
success
error ->
error
end
end
defp process_video(file) do
with {:ok, temp_path, _file} <- Storage.retrieve_file(file.uuid),
{:ok, metadata} <- extract_video_metadata(temp_path),
:ok <- update_file_with_metadata(file, metadata) do
# Generate variants
case VariantGenerator.generate_variants(file) do
{:ok, variants} ->
File.rm(temp_path)
{:ok, variants}
{:error, reason} ->
File.rm(temp_path)
{:error, reason}
end
end
end
defp process_document(file) do
if file.mime_type == "application/pdf" do
process_pdf(file)
else
with {:ok, temp_path, _file} <- Storage.retrieve_file(file.uuid),
{:ok, metadata} <- extract_document_metadata(temp_path, file.mime_type),
:ok <- update_file_with_metadata(file, metadata) do
File.rm(temp_path)
Logger.info("ProcessFileJob: Processed document file_uuid=#{file.uuid}")
{:ok, []}
end
end
end
defp process_pdf(file) do
with {:ok, temp_path} <- retrieve_and_log_file(file.uuid),
{:ok, metadata} <- extract_pdf_metadata(temp_path),
:ok <- update_and_log_metadata(file, metadata),
{:ok, variants} <- generate_and_log_variants(file) do
File.rm(temp_path)
{:ok, variants}
else
{:error, reason} = error ->
Logger.error("ProcessFileJob: Failed to process PDF: #{inspect(reason)}")
error
end
end
defp extract_pdf_metadata(temp_path) do
{:ok, metadata} = PdfProcessor.extract_metadata(temp_path)
Logger.info("ProcessFileJob: Extracted PDF metadata=#{inspect(metadata)}")
{:ok, metadata}
end
defp extract_image_metadata(file_path) do
case ImageProcessor.extract_dimensions(file_path) do
{:ok, {width, height}} ->
{
:ok,
%{
width: width,
height: height,
format: "jpeg"
}
}
{:error, reason} ->
Logger.warning("Failed to extract image metadata: #{inspect(reason)}")
{:ok, %{}}
end
end
defp extract_video_metadata(file_path) do
case System.cmd("ffprobe", [
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"stream=width,height,duration",
"-of",
"default=noprint_wrappers=1:nokey=1",
file_path
]) do
{output, 0} ->
[width, height, duration] = String.split(String.trim(output), "\n")
{
:ok,
%{
width: String.to_integer(width),
height: String.to_integer(height),
duration: String.to_float(duration) |> round()
}
}
{error, _} ->
Logger.warning("Failed to extract video metadata: #{error}")
{:ok, %{}}
end
end
defp extract_document_metadata(_file_path, "application/pdf") do
# For PDFs, we could extract page count, author, etc.
# This is a simplified version
{:ok, %{}}
end
defp extract_document_metadata(_file_path, _mime_type) do
{:ok, %{}}
end
defp update_file_with_metadata(file, metadata) do
attrs = Map.merge(%{status: "active"}, metadata)
case Storage.update_file(file, attrs) do
{:ok, _updated_file} ->
:ok
{:error, reason} ->
Logger.error("Failed to update file metadata: #{inspect(reason)}")
{:error, reason}
end
end
end