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