defmodule Uploadex.FileStorage do
@moduledoc """
Storage for Local Files.
## Opts
* `directory`: String (required for all functions) - Relative to `base_path`
* `base_path`: String (required for all functions)
* `base_url`: String (required for `c:Uploadex.Storage.get_url/2`)
To build the URL, `base_path` will be replaced by `base_url`.
## Example
To use this storage for your `User` record, define these functions in your `Uploadex.Uploader` implementation:
def default_opts(Uploadex.FileStorage), do: [base_path: :code.priv_dir(:my_app), base_url: Endpoint.url()]
def storage(%User{} = user, :photo), do: {Uploadex.FileStorage, directory: "/uploads/users"}
"""
@behaviour Uploadex.Storage
@impl true
def store(file, opts) do
full_path = get_full_path_directory(opts)
File.mkdir_p!(full_path)
store_file(file, full_path)
end
defp store_file(%{filename: filename, path: path}, directory), do: File.cp(path, Path.join(directory, filename))
defp store_file(%{filename: filename, binary: binary}, directory), do: directory |> Path.join(filename) |> File.write(binary)
@impl true
def delete(%{filename: filename}, opts), do: delete(filename, opts)
def delete(filename, opts) when is_binary(filename) do
opts
|> get_file_full_path(filename)
|> File.rm()
end
@impl true
def get_url(%{filename: filename}, opts), do: get_url(filename, opts)
def get_url(filename, opts) when is_binary(filename) do
full_path = get_file_full_path(opts, filename)
base_path = Keyword.fetch!(opts, :base_path)
base_url = Keyword.fetch!(opts, :base_url)
{:ok, String.replace(full_path, base_path, base_url)}
end
@impl true
def get_temporary_file(%{filename: filename}, path, opts), do: get_temporary_file(filename, path, opts)
def get_temporary_file(filename, path, opts) when is_binary(filename) do
delay = Keyword.get(opts, :delete_after, :timer.seconds(30))
full_path = get_file_full_path(opts, filename)
destination_file = Ecto.UUID.generate() <> Path.extname(filename)
destination_path = Path.join(path, destination_file)
File.cp!(full_path, destination_path)
delete_file_after_delay(delay, destination_path)
destination_path
end
defp delete_file_after_delay(delay, path) do
:timer.apply_after(delay, File, :rm, [path])
end
defp get_file_full_path(opts, filename) do
opts
|> get_full_path_directory()
|> Path.join(filename)
end
defp get_full_path_directory(opts) do
base_path = Keyword.fetch!(opts, :base_path)
directory = Keyword.fetch!(opts, :directory)
Path.join(base_path, directory)
end
end