Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return exceptions for all filesystem errors #44

Merged
merged 1 commit into from
Nov 14, 2023
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ If you were previously using `:monitor_pid` like this:
:ok = Briefly.give_away(path, pid)
```

### Exceptions

The following exceptions may now be returned from `Briefly.create/1`:

- `%Briefly.NoRootDirectoryError{}` - returned when a root temporary directory could not be accessed.

- `%Briefly.WriteError{}` - returned when an entry cannot be created.

## v0.4.1 (2023-01-11)

- Fix an issue with custom tmp dirs without a trailing slash ([#24](https://github.com/CargoSense/briefly/pull/24)) @srgpqt
Expand Down
18 changes: 4 additions & 14 deletions lib/briefly.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ defmodule Briefly do
@doc """
Requests a temporary file to be created with the given options.
"""
@spec create(create_opts) ::
{:ok, binary}
| {:no_space, binary}
| {:too_many_attempts, binary, pos_integer}
| {:no_tmp, [binary]}
@spec create(create_opts) :: {:ok, binary} | {:error, Exception.t()}
def create(opts \\ []) do
opts
|> Enum.into(%{})
Expand All @@ -37,17 +33,11 @@ defmodule Briefly do
Requests a temporary file to be created with the given options
and raises on failure.
"""
@spec create!(create_opts) :: binary | no_return
@spec create!(create_opts) :: binary
def create!(opts \\ []) do
case create(opts) do
{:ok, path} ->
path

{:too_many_attempts, tmp, attempts} ->
raise "tried #{attempts} times to create a temporary file at #{tmp} but failed. What gives?"

{:no_tmp, _tmps} ->
raise "could not create a tmp directory to store temporary files. Set the :briefly :directory application setting to a directory with write permission"
{:ok, path} -> path
{:error, exception} when is_exception(exception) -> raise exception
end
end

Expand Down
38 changes: 15 additions & 23 deletions lib/briefly/entry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,8 @@ defmodule Briefly.Entry do
end

def create(%{} = options) do
case ensure_tmp() do
{:ok, tmp} ->
open(options, tmp, 0)

{:no_tmp, _} = error ->
error
with {:ok, tmp} <- ensure_tmp() do
open(options, tmp, 0, nil)
end
end

Expand Down Expand Up @@ -154,7 +150,7 @@ defmodule Briefly.Entry do
if tmp = Enum.find_value(tmp_roots, &write_tmp_dir(Path.join(&1, subdir))) do
{:ok, tmp}
else
{:no_tmp, tmp_roots}
{:error, %Briefly.NoRootDirectoryError{tmp_dirs: tmp_roots}}
end
end

Expand All @@ -165,46 +161,42 @@ defmodule Briefly.Entry do
end
end

defp open(%{directory: true} = options, tmp, attempts) when attempts < @max_attempts do
defp open(%{directory: true} = options, tmp, attempts, _) when attempts < @max_attempts do
path = path(options, tmp)

case File.mkdir_p(path) do
:ok ->
:ets.insert(@path_table, {self(), path})
{:ok, path}

{:error, :enospc} ->
{:no_space, path}

{:error, reason} when reason in [:eexist, :eacces] ->
open(options, tmp, attempts + 1)
last_error = %Briefly.WriteError{code: reason, entry_type: :directory, tmp_dir: tmp}
open(options, tmp, attempts + 1, last_error)

error ->
error
{:error, code} ->
{:error, %Briefly.WriteError{code: code, entry_type: :directory, tmp_dir: tmp}}
end
end

defp open(options, tmp, attempts) when attempts < @max_attempts do
defp open(options, tmp, attempts, _) when attempts < @max_attempts do
path = path(options, tmp)

case :file.write_file(path, "", [:write, :raw, :exclusive, :binary]) do
:ok ->
:ets.insert(@path_table, {self(), path})
{:ok, path}

{:error, :enospc} ->
{:no_space, path}

{:error, reason} when reason in [:eexist, :eacces] ->
open(options, tmp, attempts + 1)
last_error = %Briefly.WriteError{code: reason, entry_type: :file, tmp_dir: tmp}
open(options, tmp, attempts + 1, last_error)

error ->
error
{:error, code} ->
{:error, %Briefly.WriteError{code: code, entry_type: :file, tmp_dir: tmp}}
end
end

defp open(_prefix, tmp, attempts) do
{:too_many_attempts, tmp, attempts}
defp open(_options, _tmp, _attempts, last_error) do
{:error, last_error}
end

defp path(options, tmp) do
Expand Down
38 changes: 38 additions & 0 deletions lib/briefly/exceptions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule Briefly.NoRootDirectoryError do
@moduledoc """
Returned when none of the root temporary directories could be accessed.
"""
@type t :: %__MODULE__{
:tmp_dirs => [String.t()]
}
defexception [:tmp_dirs]

@impl true
def message(_) do
"could not create a directory to store temporary files." <>
" Set the :briefly :directory application setting to a directory with write permission"
end
end

defmodule Briefly.WriteError do
@moduledoc """
Returned when a temporary file cannot be written.
"""
@type t :: %__MODULE__{
:code => :file.posix() | :badarg | :terminated | :system_limit,
:entry_type => :directory | :file,
:tmp_dir => String.t()
}
defexception [:code, :entry_type, :tmp_dir]

@impl true
def message(%{code: code} = e) when code in [:eexist, :eacces] do
"tried to create a temporary #{e.entry_type} in #{e.tmp_dir} but failed." <>
" Set the :briefly :directory application setting to a directory with write permission"
end

@impl true
def message(e) do
"could not write #{e.entry_type} in #{e.tmp_dir}, got: #{inspect(e.code)}"
end
end