defmodule QuickBEAM.NodeFS do
@moduledoc false
@spec read_file([String.t()]) :: {:bytes, binary()} | nil
def read_file([path]) when is_binary(path) do
case File.read(path) do
{:ok, data} -> {:bytes, data}
{:error, _} -> nil
end
end
@spec write_file(list()) :: boolean()
def write_file([path, data]) when is_binary(path) do
case File.write(path, data) do
:ok -> true
{:error, _} -> false
end
end
@spec append_file(list()) :: boolean()
def append_file([path, data]) when is_binary(path) do
case File.write(path, data, [:append]) do
:ok -> true
{:error, _} -> false
end
end
@spec exists([String.t()]) :: boolean()
def exists([path]) when is_binary(path) do
File.exists?(path)
end
@spec mkdir(list()) :: boolean()
def mkdir([path, recursive]) when is_binary(path) do
result = if recursive, do: File.mkdir_p(path), else: File.mkdir(path)
case result do
:ok -> true
{:error, :eexist} -> true
{:error, _} -> false
end
end
@spec readdir([String.t()]) :: [String.t()] | nil
def readdir([path]) when is_binary(path) do
case File.ls(path) do
{:ok, entries} -> Enum.sort(entries)
{:error, _} -> nil
end
end
@spec stat([String.t()]) :: map() | nil
def stat([path]) when is_binary(path) do
file_stat(path, false)
end
@spec lstat([String.t()]) :: map() | nil
def lstat([path]) when is_binary(path) do
file_stat(path, true)
end
@spec unlink([String.t()]) :: boolean()
def unlink([path]) when is_binary(path) do
case File.rm(path) do
:ok -> true
{:error, _} -> false
end
end
@spec rename(list()) :: boolean()
def rename([old_path, new_path]) when is_binary(old_path) and is_binary(new_path) do
case File.rename(old_path, new_path) do
:ok -> true
{:error, _} -> false
end
end
@spec rm(list()) :: boolean()
def rm([path, recursive, force]) when is_binary(path) do
result = if recursive, do: File.rm_rf(path), else: File.rm(path)
case result do
:ok -> true
{:ok, _} -> true
{:error, _, _} -> force
{:error, :enoent} -> force
{:error, _} -> false
end
end
@spec copy_file(list()) :: boolean()
def copy_file([src, dest]) when is_binary(src) and is_binary(dest) do
case File.cp(src, dest) do
:ok -> true
{:error, _} -> false
end
end
@spec realpath([String.t()]) :: String.t() | nil
def realpath([path]) when is_binary(path) do
expanded = Path.expand(path)
case :file.read_link_info(String.to_charlist(path)) do
{:ok, _} -> if File.exists?(expanded), do: expanded, else: nil
_ -> nil
end
end
defp file_stat(path, follow_links) do
stat_fn = if follow_links, do: &File.lstat/1, else: &File.stat/1
case stat_fn.(path) do
{:ok, %File.Stat{} = s} ->
epoch = fn
nil -> 0
dt -> DateTime.to_unix(dt, :millisecond)
end
%{
"size" => s.size,
"mode" => s.mode,
"type" => Atom.to_string(s.type),
"mtime" => epoch.(datetime_from_erl(s.mtime)),
"atime" => epoch.(datetime_from_erl(s.atime)),
"ctime" => epoch.(datetime_from_erl(s.ctime)),
"birthtime" => epoch.(datetime_from_erl(s.ctime))
}
{:error, _} ->
nil
end
end
defp datetime_from_erl({{y, m, d}, {h, min, s}}) do
case NaiveDateTime.new(y, m, d, h, min, s) do
{:ok, ndt} -> DateTime.from_naive!(ndt, "Etc/UTC")
_ -> nil
end
end
defp datetime_from_erl(_), do: nil
end