defmodule MyspaceIPFS.Utils do
@moduledoc """
Some common functions that are used throughout the library.
"""
@type fspath :: MyspaceIPFS.fspath()
@typep response :: Tesla.Env.t()
@typep okmapped :: MyspaceIPFS.okmapped()
@doc """
Filter out any empty values from a list.
Removes nil, {}, [], and "".
"""
@spec filter_empties(list) :: list
def filter_empties(list) do
list
|> Enum.filter(fn x -> x != nil end)
|> Enum.filter(fn x -> x != {} end)
|> Enum.filter(fn x -> x != [] end)
|> Enum.filter(fn x -> x != "" end)
end
@doc """
Extracts the data from a response. Given a response, it will structure the
data in a way that is easier to work with. IPFS only sends strings. This
function will convert the string to a list of maps.
"""
@spec handle_plain_response({:ok, response}) :: okmapped
def handle_plain_response({:ok, response}) do
with {_, tokens, _} <- :lexer.string(~c'#{response}') do
if tokens == [] do
{:ok, []}
else
tokens
# The parser returns a tuple, which means okify() is not needed.
|> :parser.parse()
end
end
end
# NB: There be dragons in here. This feels like a kludge.
# But this is a chokepoint for all the errors so it's probably fine
# and can be refactored later.
# The error response when status code is 500 can contain important
# information. This function will extract that information and return
# it as a tuple.
@doc false
@spec handle_plain_response({atom, binary}) :: any
def handle_plain_response({:eserver, response}) do
with {_, tokens, _} <- :lexer.string(~c'#{response}') do
if tokens == [] do
{:ok, []}
else
tokens
|> :parser.parse()
|> (fn {_, data} -> {data} end).()
|> Tuple.to_list()
|> List.first()
|> List.first()
|> (fn data -> {:eserver, data} end).()
end
end
end
@spec handle_plain_response(binary) :: any
def handle_plain_response(response) do
{error, {:ok, tesla_response}} = response
{error, tesla_response}
end
@doc false
@spec handle_json_response({:ok, response}) :: okmapped
def handle_json_response({:ok, response}) do
response
|> Jason.decode!()
|> okify()
end
@doc false
@spec handle_json_response({atom, binary}) :: any
def handle_json_response({error, response}) do
handle_plain_response({error, response})
end
@doc false
# This function could be overloaded to extract data from response.
@spec handle_file_response({:ok, binary}, fspath) :: okmapped
def handle_file_response({:ok, response}, output) do
File.write!(output, response)
{:ok, output}
end
@spec timestamp :: integer
@doc """
Returns the current timestamp in unix time.
"""
def timestamp() do
DateTime.utc_now()
|> DateTime.to_unix()
end
@doc """
Returns the current timestamp in iso8601 format.
"""
@spec timestamp(:iso) :: binary
def timestamp(:iso) do
DateTime.utc_now()
|> DateTime.to_iso8601()
end
@doc """
Wraps the data in an elixir standard response tuple.
{:ok, data} or {:error, data}
"""
@spec okify(any) :: {:ok, any} | {:error, any}
def okify({:error, _} = err), do: err
def okify(res), do: {:ok, res}
@doc """
Unlists a list if it only contains one element.
"""
@spec unlist(list) :: any
def unlist(list) do
case list do
[x] -> x
_ -> list
end
end
@doc false
@spec write_temp_file(binary, fspath) :: {:ok, fspath}
def write_temp_file(data, dir \\ "/tmp") do
with dir <- mktempdir(dir),
name <- Nanoid.generate(),
file <- dir <> "/" <> name do
File.write!(file, data)
{:ok, file}
end
end
@doc false
@spec mktempdir(binary) :: binary
def mktempdir(parent_dir) do
with dir <- Nanoid.generate(),
dir_path <- parent_dir <> "/myspace-" <> dir do
File.mkdir_p(dir_path)
dir_path
end
end
@doc """
Removes a temporary file. To be used in a pipe, and hence returns the data sent to it.
"""
def remove_temp_file(data, file) do
File.rm_rf!(file)
data
end
end