ExDaytona.FS (ex_daytona v0.2.0)

Copy Markdown View Source

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

chmod(sandbox, path, opts)

@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.

delete(sandbox, path, opts \\ [])

@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.

download(sandbox, remote_path, local_path)

@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.

download_file(sandbox, remote_path, local_path, opts \\ [])

@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.

download_stream(sandbox, remote_path, consumer, opts \\ [])

@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.

grep(sandbox, path, pattern)

@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}]}.

list_files(sandbox, path \\ nil)

@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{}]}.

mkdir(sandbox, path, opts \\ [])

@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").

move(sandbox, source, destination)

@spec move(ExDaytona.Sandbox.t(), String.t(), String.t()) ::
  :ok | {:error, ExDaytona.Error.t()}

Move (or rename) source to destination.

read_file(sandbox, path)

@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.

replace(sandbox, files, pattern, replacement)

@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.

search(sandbox, path, pattern)

@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]}.

stat(sandbox, 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.

upload(sandbox, local_path, remote_path)

@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.

upload_file(sandbox, local_path, remote_path, opts \\ [])

@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).

upload_stream(sandbox, remote_path, source, 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 (default 120_000)
  • :deadline — overall ms budget for the transfer
  • :cancel — zero-arity fun checked per chunk; returning true aborts 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.

write_file(sandbox, path, content)

@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.

write_files(sandbox, entries)

@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.