File-system operations inside a sandbox.
The full facade over the toolbox file-system API — writing/reading, directories, metadata, permissions, search, and text replacement:
:ok = ExDaytona.FS.mkdir(sandbox, "/workspace/data")
:ok = ExDaytona.FS.write_file(sandbox, "/workspace/data/hello.txt", "hi")
{:ok, "hi"} = ExDaytona.FS.read_file(sandbox, "/workspace/data/hello.txt")
{:ok, files} = ExDaytona.FS.list_files(sandbox, "/workspace/data")
{:ok, %ExDaytona.Model.FileInfo{}} = ExDaytona.FS.stat(sandbox, "/workspace/data/hello.txt")
{:ok, paths} = ExDaytona.FS.search(sandbox, "/workspace", "*.txt") # glob on names
{:ok, matches} = ExDaytona.FS.grep(sandbox, "/workspace", "TODO") # text in contents
:ok = ExDaytona.FS.move(sandbox, "/workspace/data/hello.txt", "/workspace/hello.txt")
:ok = ExDaytona.FS.delete(sandbox, "/workspace/data", recursive: true)ExDaytona.Sandbox.write_file/3, read_file/2, and list_files/2
delegate here — use whichever module reads better at the call site.
Summary
Functions
Set permissions/ownership on path.
Delete the file or directory at path.
Download the file at remote_path to a local path.
Stream a download to a local path safely: chunks are written
incrementally to a sibling temporary file, which is synced, verified
(:expected_sha256, when given), and atomically renamed over
local_path only on success. A failed, canceled, oversized, or
checksum-mismatched transfer removes the temporary file and never
touches an existing local_path.
Stream a download through consumer, chunk by chunk, without buffering
the file.
Search file contents under path for pattern. Returns
{:ok, [%ExDaytona.Model.Match{file, line, content}]}.
List files at path (default: the working directory). Returns
{:ok, [%ExDaytona.Model.FileInfo{}]}.
Create a directory (and parents) at path.
Move (or rename) source to destination.
Read the file at path. Returns {:ok, binary}.
Replace pattern with replacement across files. Returns
{:ok, [%ExDaytona.Model.ReplaceResult{file, success, error}]} — a
per-file report, not all-or-nothing.
Find files under path whose names match the glob pattern.
Returns {:ok, [path]}.
File metadata for path as an ExDaytona.Model.FileInfo.
Upload a local file to remote_path inside the sandbox.
Stream a local file to remote_path with constant memory. Equivalent
to upload_stream(sandbox, remote_path, {:file, local_path}, opts).
Upload from a lazy source without buffering it in memory.
Write content to path inside the sandbox (parent directories are
created by the API). Returns :ok.
Write several files in one call: entries is a list of
{path, content} tuples. Stops at the first failure.
Functions
@spec chmod(ExDaytona.Sandbox.t(), String.t(), keyword()) :: :ok | {:error, ExDaytona.Error.t()}
Set permissions/ownership on path.
Options: :mode (e.g. "644"), :owner, :group — at least one is
required.
@spec delete(ExDaytona.Sandbox.t(), String.t(), keyword()) :: :ok | {:error, ExDaytona.Error.t()}
Delete the file or directory at path.
Options: :recursive — required to delete non-empty directories.
@spec download(ExDaytona.Sandbox.t(), String.t(), Path.t()) :: :ok | {:error, ExDaytona.Error.t()}
Download the file at remote_path to a local path.
Buffered
Buffers the whole file in memory before writing. For large files use
download_file/4, which streams to a temporary file and renames
atomically.
@spec download_file(ExDaytona.Sandbox.t(), String.t(), Path.t(), keyword()) :: {:ok, %{bytes: non_neg_integer(), sha256: String.t()}} | {:error, ExDaytona.Error.t()}
Stream a download to a local path safely: chunks are written
incrementally to a sibling temporary file, which is synced, verified
(:expected_sha256, when given), and atomically renamed over
local_path only on success. A failed, canceled, oversized, or
checksum-mismatched transfer removes the temporary file and never
touches an existing local_path.
Options as in download_stream/4.
@spec download_stream( ExDaytona.Sandbox.t(), String.t(), (binary() -> any()), keyword() ) :: {:ok, %{bytes: non_neg_integer(), sha256: String.t()}} | {:error, ExDaytona.Error.t()}
Stream a download through consumer, chunk by chunk, without buffering
the file.
consumer receives each binary chunk; returning :halt cancels the
transfer (the HTTP request is closed), any other return continues.
Options: :max_bytes, :idle_timeout, :deadline,
:expected_sha256 — as in upload_stream/4.
Returns {:ok, %{bytes: n, sha256: hex}} when the stream completed.
@spec grep(ExDaytona.Sandbox.t(), String.t(), String.t()) :: {:ok, [ExDaytona.Model.Match.t()]} | {:error, ExDaytona.Error.t()}
Search file contents under path for pattern. Returns
{:ok, [%ExDaytona.Model.Match{file, line, content}]}.
@spec list_files(ExDaytona.Sandbox.t(), String.t() | nil) :: {:ok, [ExDaytona.Model.FileInfo.t()]} | {:error, ExDaytona.Error.t()}
List files at path (default: the working directory). Returns
{:ok, [%ExDaytona.Model.FileInfo{}]}.
@spec mkdir(ExDaytona.Sandbox.t(), String.t(), keyword()) :: :ok | {:error, ExDaytona.Error.t()}
Create a directory (and parents) at path.
Options: :mode — permission string (default "755").
@spec move(ExDaytona.Sandbox.t(), String.t(), String.t()) :: :ok | {:error, ExDaytona.Error.t()}
Move (or rename) source to destination.
@spec read_file(ExDaytona.Sandbox.t(), String.t()) :: {:ok, binary()} | {:error, ExDaytona.Error.t()}
Read the file at path. Returns {:ok, binary}.
Buffered
The whole file is returned as one binary. For large files use
download_stream/4 / download_file/4.
@spec replace(ExDaytona.Sandbox.t(), [String.t()], String.t(), String.t()) :: {:ok, [ExDaytona.Model.ReplaceResult.t()]} | {:error, ExDaytona.Error.t()}
Replace pattern with replacement across files. Returns
{:ok, [%ExDaytona.Model.ReplaceResult{file, success, error}]} — a
per-file report, not all-or-nothing.
@spec search(ExDaytona.Sandbox.t(), String.t(), String.t()) :: {:ok, [String.t()]} | {:error, ExDaytona.Error.t()}
Find files under path whose names match the glob pattern.
Returns {:ok, [path]}.
@spec stat(ExDaytona.Sandbox.t(), String.t()) :: {:ok, ExDaytona.Model.FileInfo.t()} | {:error, ExDaytona.Error.t()}
File metadata for path as an ExDaytona.Model.FileInfo.
@spec upload(ExDaytona.Sandbox.t(), Path.t(), String.t()) :: :ok | {:error, ExDaytona.Error.t()}
Upload a local file to remote_path inside the sandbox.
Buffered
Reads the file fully into memory (File.read/1). For large files
use upload_file/4, which streams with constant memory.
@spec upload_file(ExDaytona.Sandbox.t(), Path.t(), String.t(), keyword()) :: {:ok, %{bytes: non_neg_integer(), sha256: String.t()}} | {:error, ExDaytona.Error.t()}
Stream a local file to remote_path with constant memory. Equivalent
to upload_stream(sandbox, remote_path, {:file, local_path}, opts).
@spec upload_stream(ExDaytona.Sandbox.t(), String.t(), term(), keyword()) :: {:ok, %{bytes: non_neg_integer(), sha256: String.t()}} | {:error, ExDaytona.Error.t()}
Upload from a lazy source without buffering it in memory.
source may be an Enumerable of iodata chunks, an IO device
(pid/atom — read with IO.binstream/2), or {:file, path} (read with
File.stream!/2; File.read/1 is never used on this path).
Options
:max_bytes— abort the request once the source exceeds this size:idle_timeout— max ms between response events (default120_000):deadline— overall ms budget for the transfer:cancel— zero-arity fun checked per chunk; returningtrueaborts the request:expected_sha256— hex digest to verify the streamed bytes against:chunk_size— read size for file/IO sources (default 64 KiB)
Returns {:ok, %{bytes: n, sha256: hex}} — the SHA-256 is computed
incrementally while streaming.
@spec write_file(ExDaytona.Sandbox.t(), String.t(), iodata()) :: :ok | {:error, ExDaytona.Error.t()}
Write content to path inside the sandbox (parent directories are
created by the API). Returns :ok.
Buffered
content is held fully in memory — appropriate for small control
files. For large payloads use upload_stream/4 / upload_file/4.
@spec write_files(ExDaytona.Sandbox.t(), [{String.t(), iodata()}]) :: :ok | {:error, ExDaytona.Error.t()}
Write several files in one call: entries is a list of
{path, content} tuples. Stops at the first failure.