Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions lib/waffle/actions/store.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,18 @@ defmodule Waffle.Actions.Store do
defp put(_definition, {error = {:error, _msg}, _scope}), do: error

defp put(definition, {%Waffle.File{} = file, scope}) do
with {:ok, file} <- validate(definition, {file, scope}),
{:ok, file} <- normalize(definition, {file, scope}) do
definition
|> put_versions({file, scope})
|> cleanup!(file)
end
end

defp validate(definition, {file, scope}) do
case definition.validate({file, scope}) do
result when result == true or result == :ok ->
put_versions(definition, {file, scope})
|> cleanup!(file)
{:ok, file}

{:error, message} ->
{:error, message}
Expand All @@ -81,6 +89,19 @@ defmodule Waffle.Actions.Store do
end
end

defp normalize(definition, {file, scope}) do
case definition.normalize({file, scope}) do
{:ok, %Waffle.File{} = file} ->
{:ok, file}

{:error, message} ->
{:error, message}

_ ->
{:error, :normalization_error}
end
end

defp put_versions(definition, {file, scope}) do
if definition.async() do
definition.__versions()
Expand Down Expand Up @@ -158,5 +179,4 @@ defmodule Waffle.Actions.Store do

result
end

end
23 changes: 23 additions & 0 deletions lib/waffle/definition/storage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ defmodule Waffle.Definition.Storage do
Any other return value will return `{:error, :invalid_file}` when passed through
to `Avatar.store`.

## File Normalization

The `normalize/1` function is used to normalize the file before processing.
The function must return either `{:ok, %Waffle.File{}}` or `{:error, message}`.

Here is an example of generating a unique file name using the `normalize` function:

defmodule Avatar do
use Waffle.Definition

def normalize({file, _}) do
ext = file.file_name |> Path.extname() |> String.downcase()
new_file_name = :crypto.strong_rand_bytes(20) |> Base.encode32(case: :lower) |> Kernel.<>(ext)
new_path = file.path |> Path.dirname() |> Path.join(new_file_name)

with :ok <- File.rename(file.path, new_path) do
{:ok, %{file | path: new_path, file_name: new_file_name}}
end
end
end

## Passing custom headers when downloading from remote path

By default, when downloading files from remote path request headers are empty,
Expand Down Expand Up @@ -120,6 +141,7 @@ defmodule Waffle.Definition.Storage do
def storage_dir_prefix, do: Application.get_env(:waffle, :storage_dir_prefix, "")
def storage_dir(_, _), do: Application.get_env(:waffle, :storage_dir, "uploads")
def validate(_), do: true
def normalize({file, _scope}), do: {:ok, file}
def default_url(version, _), do: default_url(version)
def default_url(_), do: nil
def __storage, do: Application.get_env(:waffle, :storage, Waffle.Storage.S3)
Expand All @@ -128,6 +150,7 @@ defmodule Waffle.Definition.Storage do
storage_dir: 2,
filename: 2,
validate: 1,
normalize: 1,
default_url: 1,
default_url: 2,
__storage: 0,
Expand Down
25 changes: 25 additions & 0 deletions test/actions/store_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ defmodule WaffleTest.Actions.Store do
def __versions, do: [:original, :thumb, :skipped]
end

defmodule DummyDefinitionWithNormalization do
use Waffle.Actions.Store
use Waffle.Definition.Storage

def normalize({file, _scope}) do
ext = file.file_name |> Path.extname() |> String.downcase()
new_file_name = file.file_name |> Path.basename(ext) |> Kernel.<>("_normalized#{ext}")
new_path = storage_dir_prefix() |> Path.join(new_file_name)

with :ok <- File.cp(file.path, new_path) do
{:ok, %{file | path: new_path, file_name: new_file_name}}
end
end

def storage_dir_prefix, do: "waffletest"

def transform(_, _), do: :noaction
def __versions, do: [:original]
def __storage, do: Waffle.Storage.Local
end

test "custom transformations change a file extension" do
with_mock Waffle.Storage.S3,
put: fn DummyDefinitionWithExtension, _, {%{file_name: "image.jpg", path: _}, nil} ->
Expand All @@ -76,6 +97,10 @@ defmodule WaffleTest.Actions.Store do
assert DummyDefinitionWithValidationError.store(__ENV__.file) == {:error, "invalid file type"}
end

test "normalizes file" do
assert DummyDefinitionWithNormalization.store(@img) == {:ok, "image_normalized.png"}
end

test "single binary argument is interpreted as file path" do
with_mock Waffle.Storage.S3,
put: fn DummyDefinition, _, {%{file_name: "image.png", path: @img}, nil} ->
Expand Down