Current section

Files

Jump to
temp lib temp.ex
Raw

lib/temp.ex

defmodule Temp do
@type options :: nil | Path.t | map
@doc """
Returns `:ok` when the tracking server used to track temporary files started properly.
"""
@spec track :: Agent.on_start
def track do
case GenServer.start_link(Temp.Tracker, nil, name: TempTracker) do
{:ok, _} -> :ok
err -> err
end
end
@doc """
Same as `track/1`, but raises an exception on failure. Otherwise, returns `:ok`
"""
@spec track! :: pid
def track! do
case track do
:ok -> :ok
err -> raise Temp.Error, message: err
end
end
@doc """
Return the paths currently tracked.
"""
@spec tracked :: Set.t
def tracked do
GenServer.call(TempTracker, :tracked)
end
@doc """
Cleans up the temporary files tracked
"""
@spec cleanup :: :ok | {:error, any}
def cleanup do
GenServer.call(TempTracker, :cleanup)
end
@doc """
Returns a `{:ok, path}` where `path` is a path that can be used freely in the
system temporary directory, or `{:error, reason}` if it fails to get the
system temporary directory.
"""
@spec path(options) :: {:ok, Path.t} | {:error, String.t}
def path(options \\ nil) do
case generate_name(options, "f") do
{:ok, path, _} -> {:ok, path}
err -> err
end
end
@doc """
Same as `path/1`, but raises an exception on failure. Otherwise, returns a temporary path.
"""
@spec path!(options) :: Path.t
def path!(options \\ nil) do
case path(options) do
{:ok, path} -> path
err -> err
end
end
@doc """
Returns `{:ok, fd, file_path}` if no callback is passed, or `{:ok, file_path}`
if callback is passed, where `fd` is the file descriptor of a temporary file
and `file_path` is the path of the temporary file.
When no callback is passed, the file descriptor should be closed.
Returns `{:error, reason}` if a failure occurs.
"""
@spec open(options, nil | (File.io_device -> any)) :: {:ok, File.io_device, Path.t} | {:error, any}
def open(options \\ nil, func \\ nil) do
case generate_name(options, "f") do
{:ok, path, options} ->
options = Dict.put(options, :mode, options[:mode] || [:read, :write])
ret = if func do
File.open(path, options[:mode], func)
else
File.open(path, options[:mode])
end
case ret do
{:ok, res} ->
if tracking?, do: register_path(path)
if func, do: {:ok, path}, else: {:ok, res, path}
err -> err
end
err -> err
end
end
@doc """
Same as `open/1`, but raises an exception on failure.
"""
@spec open!(options, pid | nil) :: {File.io_device, Path.t}
def open!(options \\ nil, func \\ nil) do
case open(options, func) do
{:ok, res, path} -> {res, path}
{:error, err} -> raise Temp.Error, message: err
end
end
@doc """
Returns `{:ok, dir_path}` where `dir_path` is the path is the path of the
created temporary directory.
Returns `{:error, reason}` if a failure occurs.
"""
@spec mkdir(options) :: {:ok, Path.t} | {:error, any}
def mkdir(options \\ %{}) do
case generate_name(options, "d") do
{:ok, path, _} ->
case File.mkdir path do
:ok ->
if tracking?, do: register_path(path)
{:ok, path}
err -> err
end
err -> err
end
end
@doc """
Same as `mkdir/1`, but raises an exception on failure. Otherwise, returns
a temporary directory path.
"""
@spec mkdir!(options) :: Path.t
def mkdir!(options \\ %{}) do
case mkdir(options) do
{:ok, path} ->
if tracking?, do: register_path(path)
path
{:error, err} -> raise Temp.Error, message: err
end
end
@spec generate_name(options, Path.t) :: {:ok, Path.t, map} | {:error, String.t}
defp generate_name(options, default_prefix) do
case prefix(options) do
{:ok, path} ->
affixes = parse_affixes(options, default_prefix)
name = Path.join(path, [
affixes[:prefix],
"-",
timestamp,
"-",
:os.getpid,
"-",
random_string,
"-",
affixes[:suffix]
] |> Enum.join)
{:ok, name, affixes}
err -> err
end
end
@spec prefix(nil | map) :: {:ok, Path.t} | {:error, String.t}
defp prefix(%{basedir: dir}), do: {:ok, dir}
defp prefix(_) do
case System.tmp_dir do
nil -> {:error, "no tmp_dir readable"}
path -> {:ok, path}
end
end
@spec parse_affixes(options, Path.t) :: map
defp parse_affixes(nil, default_prefix), do: %{prefix: default_prefix}
defp parse_affixes(affixes, _) when is_bitstring(affixes), do: %{prefix: affixes, suffix: ""}
defp parse_affixes(affixes, default_prefix) when is_map(affixes) do
affixes
|> Dict.put(:prefix, affixes[:prefix] || default_prefix)
|> Dict.put(:suffix, affixes[:suffix] || "")
end
defp parse_affixes(_, default_prefix) do
%{prefix: default_prefix, suffix: ""}
end
defp tracking? do
Enum.any?(Process.registered, fn x -> x == TempTracker end)
end
defp register_path(path) do
GenServer.call(TempTracker, {:add, path})
end
defp timestamp do
{ms, s, _} = :os.timestamp
Integer.to_string(ms * 1_000_000 + s)
end
defp random_string do
Integer.to_string(:random.uniform(0x100000000), 36) |> String.downcase
end
end