Current section

Files

Jump to
reactor_file lib reactor file ops.ex
Raw

lib/reactor/file/ops.ex

# SPDX-FileCopyrightText: 2025 reactor_file contributors <https://github.com/ash-project/reactor_file/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Reactor.File.Ops do
@moduledoc false
alias Reactor.File.{FileError, MissingMiddlewareError}
require Reactor.File.FileError
@doc "An error wrapped version of `File.chgrp/2`"
def chgrp(path, gid, step, message \\ "Unable to change group") do
with {:error, reason} when FileError.is_posix(reason) <- File.chgrp(path, gid) do
{:error,
FileError.exception(
action: :chgrp,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.chmod/2`"
def chmod(path, mode, step, message \\ "Unable to change permissions") do
with {:error, reason} when FileError.is_posix(reason) <- File.chmod(path, mode) do
{:error,
FileError.exception(
action: :chmod,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.chown/2`"
def chown(path, uid, step, message \\ "Unable to change owner") do
with {:error, reason} when FileError.is_posix(reason) <- File.chown(path, uid) do
{:error,
FileError.exception(
action: :chown,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.close/1`"
def close_file(device, step, message \\ "Unable to close IO device") do
with {:error, reason} when FileError.is_posix(reason) <-
File.close(device) do
{:error,
FileError.exception(
action: :close,
step: step,
message: message,
file: device,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.cp/2`"
def cp(source_path, destination_path, step, message \\ "Unable to copy file") do
with {:error, reason} when FileError.is_posix(reason) <-
File.cp(source_path, destination_path) do
{:error,
FileError.exception(
action: {:cp, source_path, destination_path},
step: step,
message: message,
file: destination_path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.cp/2`"
def cp_r(source_path, destination_path, options, step, message \\ "Unable to copy file") do
case File.cp_r(source_path, destination_path, options) do
{:ok, _} ->
:ok
{:error, reason, fail_path} when FileError.is_posix(reason) ->
{:error,
FileError.exception(
action: {:cp_r, source_path, destination_path},
step: step,
message: message,
file: fail_path,
reason: reason
)}
end
end
@doc "An error wrapped version of `IO.binread/2`"
def io_binread(device, line_or_chars, step, message \\ "Unable to read from device") do
with {:error, reason} when FileError.is_posix(reason) <-
IO.binread(device, line_or_chars) do
{:error,
FileError.exception(
action: {:io_binread, line_or_chars},
step: step,
message: message,
file: device,
reason: reason
)}
end
end
@doc "An error wrapped version of `IO.read/2`"
def io_read(device, line_or_chars, step, message \\ "Unable to read from device") do
with {:error, reason} when FileError.is_posix(reason) <-
IO.read(device, line_or_chars) do
{:error,
FileError.exception(
action: {:io_read, line_or_chars},
step: step,
message: message,
file: device,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.ln/2`"
def ln(existing, new, step, message \\ "Unable to create hard link") do
with {:error, reason} when FileError.is_posix(reason) <-
File.ln(existing, new) do
{:error,
FileError.exception(
action: {:ln, existing, new},
step: step,
message: message,
file: new,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.ln_s/2`"
def ln_s(existing, new, step, message \\ "Unable to create symbolic link") do
with {:error, reason} when FileError.is_posix(reason) <-
File.ln_s(existing, new) do
{:error,
FileError.exception(
action: {:ln_s, existing, new},
step: step,
message: message,
file: new,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.lstat/2`"
def lstat(path, opts, step, message \\ "Unable to retrieve file information") do
with {:error, reason} when FileError.is_posix(reason) <- File.lstat(path, opts) do
{:error,
FileError.exception(
action: :lstat,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.mkdir/1`"
def mkdir(path, step, message \\ "Unable to create directory") do
with {:error, reason} when FileError.is_posix(reason) <- File.mkdir(path) do
{:error,
FileError.exception(
action: :mkdir,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.mkdir_p/1`"
def mkdir_p(path, step, message \\ "Unable to create directory") do
with {:error, reason} when FileError.is_posix(reason) <- File.mkdir_p(path) do
{:error,
FileError.exception(
action: :mkdir_p,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.open/2`"
def open_file(path, modes, step, message \\ "Unable to open file") do
with {:error, reason} when FileError.is_posix(reason) <- File.open(path, modes) do
{:error,
FileError.exception(
action: {:open_file, modes},
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.read/1`"
def read_file(path, step, message \\ "Unable to read file") do
with {:error, reason} when FileError.is_posix(reason) <- File.read(path) do
{:error,
FileError.exception(
action: :read_file,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.lstat/2`"
def read_link(path, step, message \\ "Unable to read link") do
with {:error, reason} when FileError.is_posix(reason) <- File.read_link(path) do
{:error,
FileError.exception(
action: :read_link,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.rmdir/1`"
def rmdir(path, step, message \\ "Unable to remove directory") do
with {:error, reason} when FileError.is_posix(reason) <- File.rmdir(path) do
{:error,
FileError.exception(
action: :rmdir,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.rm_rf/1`"
def rm_rf(path, step, message \\ "Unable to recursively delete") do
case File.rm_rf(path) do
{:ok, _} ->
:ok
{:error, reason, fail_path} when FileError.is_posix(reason) ->
{:error,
FileError.exception(
action: {:rm_rf, path},
step: step,
message: message,
file: fail_path,
reason: reason
)}
{:error, reason, _} ->
{:error, reason}
end
end
@doc "An error wrapped version of `File.rm/1`"
def rm(path, step, message \\ "Unable to delete file") do
with {:error, reason} when FileError.is_posix(reason) <- File.rm(path) do
{:error,
FileError.exception(
action: :rm,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.stat/2`"
def stat(path, opts, step, message \\ "Unable to retrieve file information") do
with {:error, reason} when FileError.is_posix(reason) <- File.stat(path, opts) do
{:error,
FileError.exception(
action: :stat,
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.touch/2`"
def touch(path, time, step, message \\ "Unable to touch file") do
with {:error, reason} when FileError.is_posix(reason) <- File.touch(path, time) do
{:error,
FileError.exception(
action: {:touch, time},
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.write/3`"
def write_file(path, content, modes, step, message \\ "Unable to write file") do
with {:error, reason} when FileError.is_posix(reason) <- File.write(path, content, modes) do
{:error,
FileError.exception(
action: {:write_file, modes},
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "An error wrapped version of `File.write_stat/2`"
def write_stat(path, stat, opts, step, message \\ "Unable to write file information") do
with {:error, reason} when FileError.is_posix(reason) <- File.write_stat(path, stat, opts) do
{:error,
FileError.exception(
action: {:write_stat, stat},
step: step,
message: message,
file: path,
reason: reason
)}
end
end
@doc "A stat which doesn't fail when the file doesn't exist"
def maybe_stat(path, opts, step, message \\ "Unable to retrieve file information") do
if File.exists?(path) do
stat(path, opts, step, message)
else
{:ok, nil}
end
end
@doc "Places a copy of a file into the undo stash"
def backup_file(path, context, message \\ "Unable to backup file")
def backup_file(path, %{Reactor.File.Middleware => %{tmp_dir: tmp_dir}} = context, message) do
backup_file_name = {context.current_step, path} |> :erlang.phash2() |> Integer.to_string(16)
backup_path = Path.join(tmp_dir, backup_file_name)
with :ok <- cp(path, backup_path, message) do
{:ok, backup_path}
end
end
def backup_file(_path, context, message),
do: {:error, MissingMiddlewareError.exception(message: message, step: context.current_step)}
@doc "Places a copy of a directory into the undo stash"
def backup_dir(path, context, message \\ "Unable to backup dir")
def backup_dir(path, %{Reactor.File.Middleware => %{tmp_dir: tmp_dir}} = context, message) do
backup_dir_name = {context.current_step, path} |> :erlang.phash2() |> Integer.to_string(16)
backup_path = Path.join(tmp_dir, backup_dir_name)
with :ok <- mkdir_p(backup_path, context.current_step, message),
:ok <- cp_r(path, backup_path, [], context.current_step, message) do
{:ok, backup_path}
end
end
end