Apero.VFS (Apero v1.0.0)

Copy Markdown View Source

Virtual File System — unified file, path and filesystem utilities for Apero.

Combines path operations, high-level file actions (atomic writes, checksums, temp resources, file locking) and file discovery.

Concurrent operations (batch checksums, multi-file copies) use Arrea.Parallel.run_sync for parallelism.

Security note

atomic_write/2 always creates the temp file in the same directory as the target to guarantee rename(2) atomicity (cross-device renames fall back to copy+delete).

Summary

Functions

Writes content to path atomically using a temp-file + rename.

Returns the filename without extension.

Computes the checksum of a file by streaming its contents in 64KB chunks. Never loads the entire file into memory.

Computes checksums for multiple files in parallel.

Copies source to dest. Creates parent directories as needed. Returns {:ok, bytes_copied}.

Recursively copies a directory from source to dest.

Copies multiple files in parallel using Arrea.Parallel.run_sync.

Deletes a file. Returns :ok even if the file did not exist.

Recursively deletes a directory and all its contents.

Returns true if the given path is an existing directory.

Returns disk usage information for the filesystem containing path.

Creates the directory and all parents. Idempotent.

Returns true if the given path exists (file, directory or symlink).

Expands ~ and relative path segments to an absolute path.

Returns the extension of a file path.

Returns true if the given path is an existing regular file.

Generates a visual ASCII tree from a list of file paths.

Lists files matching a glob pattern in dir.

Moves source to dest. Uses rename when possible, falls back to copy+delete on cross-device moves.

Returns the last modification time of a file as a NaiveDateTime.

Prints an ASCII directory tree of path to stdout.

Reads a text file and returns its contents.

Reads all non-empty, non-comment lines from a file.

Returns the file size in bytes.

Creates a symbolic link at link_path pointing to target.

Stops a file watcher by PID.

Starts watching dirs for file system changes.

Acquires an advisory lock on lock_path, runs fun, then releases it.

Creates a temporary directory, yields its path to fun, then deletes it. The directory is deleted even when fun raises.

Creates a temporary file, yields its path to fun, then deletes it. The file is deleted even when fun raises.

Writes text to a file, creating parent directories as needed.

Functions

atomic_write(path, content)

@spec atomic_write(binary(), iodata()) :: :ok | {:error, binary()}

Writes content to path atomically using a temp-file + rename.

The temp file is created in the same directory as path to ensure the rename is atomic on POSIX systems. Parent directories are created as needed.

Examples

iex> path = Path.join(System.tmp_dir!(), "apero_atomic_test.txt")
iex> :ok = Apero.VFS.atomic_write(path, "hello")
iex> File.read!(path)
"hello"
iex> File.rm!(path)
:ok

basename(path)

@spec basename(binary()) :: binary()

Returns the filename without extension.

Examples

iex> Apero.VFS.basename("path/to/file.ex")
"file"

checksum(path, algo \\ :sha256)

@spec checksum(binary(), :sha256 | :sha512 | :md5 | :sha) ::
  {:ok, binary()} | {:error, binary()}

Computes the checksum of a file by streaming its contents in 64KB chunks. Never loads the entire file into memory.

Supported algorithms: :sha256, :sha512, :md5, :sha. Returns the lowercase hex-encoded digest.

Examples

iex> path = Path.join(System.tmp_dir!(), "apero_cs_test.bin")
iex> File.write!(path, "hello")
iex> {:ok, digest} = Apero.VFS.checksum(path, :sha256)
iex> String.length(digest)
64
iex> File.rm!(path)
:ok

checksum_many(paths, algo \\ :sha256)

@spec checksum_many([binary()], :sha256 | :sha512 | :md5 | :sha) :: %{
  required(binary()) => {:ok, binary()} | {:error, binary()}
}

Computes checksums for multiple files in parallel.

Returns a map of path => {:ok, digest} | {:error, reason}.

copy(source, dest)

@spec copy(binary(), binary()) :: {:ok, non_neg_integer()} | {:error, binary()}

Copies source to dest. Creates parent directories as needed. Returns {:ok, bytes_copied}.

copy_dir(source, dest)

@spec copy_dir(binary(), binary()) :: :ok | {:error, binary()}

Recursively copies a directory from source to dest.

copy_many(pairs)

@spec copy_many([{binary(), binary()}]) :: [ok: non_neg_integer(), error: binary()]

Copies multiple files in parallel using Arrea.Parallel.run_sync.

Each element of pairs is a {source, dest} tuple. Returns [{:ok, bytes} | {:error, reason}] in the same order.

delete(path)

@spec delete(binary()) :: :ok | {:error, binary()}

Deletes a file. Returns :ok even if the file did not exist.

delete_dir(path)

@spec delete_dir(binary()) :: :ok | {:error, binary()}

Recursively deletes a directory and all its contents.

dir?(path)

@spec dir?(binary()) :: boolean()

Returns true if the given path is an existing directory.

disk_usage(path \\ "/")

@spec disk_usage(binary()) :: {:ok, map()} | {:error, binary()}

Returns disk usage information for the filesystem containing path.

Returns a map with :total_mb, :used_mb, :free_mb, :use_pct. Only supported on Unix-like systems.

ensure_dir(path)

@spec ensure_dir(binary()) :: :ok | {:error, File.posix()}

Creates the directory and all parents. Idempotent.

exists?(path)

@spec exists?(binary()) :: boolean()

Returns true if the given path exists (file, directory or symlink).

expand(path)

@spec expand(binary()) :: binary()

Expands ~ and relative path segments to an absolute path.

extension(path)

@spec extension(binary()) :: binary()

Returns the extension of a file path.

Examples

iex> Apero.VFS.extension("archive.tar.gz")
".gz"

file?(path)

@spec file?(binary()) :: boolean()

Returns true if the given path is an existing regular file.

generate_tree(paths)

@spec generate_tree([binary()]) :: binary()

Generates a visual ASCII tree from a list of file paths.

Returns a printable string with ├─ and └─ connectors. The last item at each level uses └─, all others use ├─.

Examples

iex> Apero.VFS.generate_tree(["a"])
"└─ a"

glob(dir, pattern \\ "*", opts \\ [])

@spec glob(binary(), binary(), keyword()) :: [binary()]

Lists files matching a glob pattern in dir.

Options

  • :recursive — recurse into subdirectories (default: false)

move(source, dest)

@spec move(binary(), binary()) :: :ok | {:error, binary()}

Moves source to dest. Uses rename when possible, falls back to copy+delete on cross-device moves.

mtime(path)

@spec mtime(binary()) :: {:ok, NaiveDateTime.t()} | {:error, File.posix()}

Returns the last modification time of a file as a NaiveDateTime.

read(path)

@spec read(binary()) :: {:ok, binary()} | {:error, binary()}

Reads a text file and returns its contents.

read_lines(path)

@spec read_lines(binary()) :: {:ok, [binary()]} | {:error, binary()}

Reads all non-empty, non-comment lines from a file.

Lines starting with # and blank lines are removed. Content is trimmed.

size(path)

@spec size(binary()) :: {:ok, non_neg_integer()} | {:error, File.posix()}

Returns the file size in bytes.

symlink(target, link_path)

@spec symlink(binary(), binary()) :: :ok | {:error, binary()}

Creates a symbolic link at link_path pointing to target.

unwatch(pid)

@spec unwatch(pid()) :: :ok

Stops a file watcher by PID.

watch(dirs, callback, opts \\ [])

@spec watch([binary()], ([{binary(), [atom()]}] -> any()), keyword()) ::
  {:ok, pid()} | {:error, term()}

Starts watching dirs for file system changes.

Calls callback with a list of {path, events} tuples after each debounce window.

Options

  • :debounce_ms — debounce delay in ms (default: 100)
  • :name — optional name for the watcher process

Returns {:ok, pid}.

with_lock(lock_path, opts \\ [], fun)

@spec with_lock(binary(), keyword(), (-> any())) :: any() | {:error, :timeout}

Acquires an advisory lock on lock_path, runs fun, then releases it.

Retries on EEXIST until timeout. Returns {:error, :timeout} if the lock cannot be acquired within the deadline.

Options

  • :timeout_ms — how long to wait in ms (default: 5_000)
  • :retry_ms — poll interval in ms (default: 100)

with_tmp_dir(opts \\ [], fun)

@spec with_tmp_dir(
  keyword(),
  (binary() -> any())
) :: any()

Creates a temporary directory, yields its path to fun, then deletes it. The directory is deleted even when fun raises.

with_tmp_file(opts \\ [], fun)

@spec with_tmp_file(
  keyword(),
  (binary() -> any())
) :: any()

Creates a temporary file, yields its path to fun, then deletes it. The file is deleted even when fun raises.

Options

  • :suffix — file extension suffix (default: "")
  • :dir — parent directory (default: System.tmp_dir!/0)

write(path, content)

@spec write(binary(), iodata()) :: :ok | {:error, binary()}

Writes text to a file, creating parent directories as needed.