# `Magpie.Storage`
[🔗](https://github.com/alexcassol/magpie/blob/v0.8.0/lib/magpie/storage.ex#L1)

A small object-storage-style API backed by Dropbox.

`Storage` is the convenient entry point for common application operations;
`Magpie.Files` remains available when Dropbox-specific controls are needed.

    alias Magpie.Storage

    {:ok, %Magpie.FileMetadata{}} =
      Storage.put(client, "/reports/today.json", {:binary, ~s({"ok":true})})

    {:ok, contents} = Storage.get(client, "/reports/today.json")
    {:ok, "/tmp/today.json"} =
      Storage.download(client, "/reports/today.json", "/tmp/today.json")

For bounded listings and application-owned checkpoints use `list_page/3` and
`continue_list/3`. These return `Magpie.ListPage`; cursor resets explicitly
require reconstruction through `Magpie.CursorError`. The consumer decides when
to save a checkpoint. Existing `list/3` and `stream/3` remain unchanged.

Upload sources are explicit:

  * `{:file, path}` streams a local file and automatically selects a single
    request or an upload session from its size;
  * `{:binary, iodata}` uploads in-memory content and makes the same choice;
  * `{:stream, enumerable}` always uses an upload session, buffering no more
    than one configured chunk between requests.

Paths are Dropbox paths, not S3 bucket/key pairs. Temporary download URLs
expire according to Dropbox's rules (normally after four hours).

All operations accept `request: [...]` to override client HTTP options,
retries and execution budget. Unknown/duplicate options and invalid values
raise `ArgumentError` before I/O. Operational errors remain error tuples;
bang functions and lazy streams raise. See the
[configuration guide](configuration.html) for contracts and precedence.

# `result`

```elixir
@type result(value) :: {:ok, value} | {:error, Exception.t() | File.posix()}
```

# `source`

```elixir
@type source() :: {:file, Path.t()} | {:binary, iodata()} | {:stream, Enumerable.t()}
```

# `continue_list`

```elixir
@spec continue_list(Magpie.Client.t(), binary(), keyword()) ::
  {:ok, Magpie.ListPage.t()} | {:error, Exception.t()}
```

Fetches one page from a saved cursor, including changes after the final page.

Only `request: [...]` is accepted; the opaque cursor retains the original
listing settings. Use it with the same account and namespace. Empty or
non-binary cursors raise `ArgumentError` before I/O.

Returns `{:ok, %Magpie.ListPage{}}` or `{:error, exception}`. Dropbox's 409
`reset` becomes `%Magpie.CursorError{reason: :reset, rebuild_required: true}`
with the original API error in `:error`. A missing folder (`path`) and other
API errors remain `Magpie.Error`; HTTP 400 errors are not guessed to be resets.
Never silently restarts a listing or persists a checkpoint.

# `copy`

Copies a Dropbox file or folder to `destination`.

# `copy!`

Like `copy/4`, but returns metadata directly and raises on failure.

# `delete`

Deletes a Dropbox file or folder and returns its final metadata.

# `delete!`

Like `delete/3`, but returns metadata directly and raises on failure.

# `delete_many`

Deletes several keys concurrently while preserving input order and isolating failures.

Accepts the same batch options as `put_many/3`; remaining options are
passed to every deletion.

# `download`

```elixir
@spec download(Magpie.Client.t(), binary(), Path.t(), keyword()) :: result(Path.t())
```

Streams `key` to a local destination and returns that destination.

The destination's parent must exist unless `mkdir_p: true` is passed.
An existing destination is replaced only after Dropbox successfully sends
the complete response. A `:progress` callback receives
`(transferred, total)` as bytes arrive; pass the expected byte count as
`:size` when it is known, otherwise `total` is `nil`.

# `download!`

Like `download/4`, but returns the destination directly and raises on failure.

# `exists?`

```elixir
@spec exists?(Magpie.Client.t(), binary(), keyword()) ::
  boolean() | {:error, Exception.t()}
```

Returns `true`, `false` for a missing key, or an error tuple for other failures.

# `get`

```elixir
@spec get(Magpie.Client.t(), binary(), keyword()) :: result(binary() | map())
```

Downloads `key` into memory and returns its bytes.

Pass `with_headers: true` to retain the `%{body: body, headers: headers}`
response shape used by `Magpie.Files.download/2`.

# `get!`

Like `get/3`, but returns the bytes directly and raises on failure.

# `list`

```elixir
@spec list(Magpie.Client.t(), binary(), keyword()) ::
  {:ok, [Magpie.Metadata.t()]} | {:error, Exception.t()}
```

Returns all entries below `prefix`, following every cursor page.

Unlike the lazy `stream/3`, this eager convenience returns Dropbox API and
Req transport failures as `{:error, exception}`. This lets background jobs
handle a failed listing without crashing the worker.

# `list!`

Like `list/3`, but returns entries directly and raises on failure.

# `list_page`

```elixir
@spec list_page(Magpie.Client.t(), binary(), keyword()) ::
  {:ok, Magpie.ListPage.t()} | {:error, Exception.t()}
```

Fetches one bounded listing page as `{:ok, %Magpie.ListPage{}}`.

Accepts the same options as `list/3` (`:recursive`, `:include_deleted`,
`:limit`, other listing flags and `request: [...]`). Dropbox's limit is
a hint, not a guaranteed page size. No subsequent page is fetched.
Save the returned cursor only after successfully applying the whole page,
including empty pages. See the [incremental guide](incremental.md).

API failures return `Magpie.Error`; transport failures and execution budgets
retain their existing exception types. Invalid options raise `ArgumentError`.

# `mkdir`

Creates a Dropbox folder at `key`.

# `mkdir!`

Like `mkdir/3`, but returns metadata directly and raises on failure.

# `move`

Moves a Dropbox file or folder to `destination`.

# `move!`

Like `move/4`, but returns metadata directly and raises on failure.

# `put`

```elixir
@spec put(Magpie.Client.t(), binary(), source(), keyword()) ::
  result(Magpie.FileMetadata.t()) | {:ok, :unchanged, Magpie.FileMetadata.t()}
```

Uploads a file, binary/iodata value, or stream to `key`.

Options are `:mode`, `:if_rev`, `:autorename`, `:mute`, `:chunk_size`,
`:session_threshold`, `:verify`, `:skip_unchanged` and `:progress`.
`:if_rev` performs a conditional update, `:verify` compares Dropbox's
content hash after upload, and `:skip_unchanged` avoids uploading matching
file/binary sources. Progress callbacks receive `(transferred, total)`;
`total` can be `nil` for streams.

With `:if_rev`, the conditional upload always runs, even when
`skip_unchanged: true`: a metadata lookup cannot atomically enforce a
revision precondition. `:if_rev` takes precedence over `:mode`.

The default is `mode: "add", autorename: true`, which can create another
file when the path exists. Use `mode: "overwrite"` to replace content,
or `if_rev: revision` for a conditional update.

# `put!`

Like `put/4`, but raises on failure.

Returns metadata directly after an upload, or `{:unchanged, metadata}` when
`skip_unchanged: true` finds identical remote content.

# `put_many`

Uploads several `{key, source}` or `{key, source, options}` entries concurrently.

Results keep input order and each item is isolated as `{key, result}`. Batch
options are `:max_concurrency`, `:timeout` and a two-argument
`:on_progress` callback receiving `(key, result)`; remaining options are
passed to every upload.

# `stat`

Returns typed Dropbox metadata for `key`.

# `stat!`

Like `stat/3`, but returns metadata directly and raises on failure.

# `stream`

```elixir
@spec stream(Magpie.Client.t(), binary(), keyword()) :: Enumerable.t()
```

Returns a lazy stream over every entry below `prefix`.

# `upload_url`

```elixir
@spec upload_url(Magpie.Client.t(), binary(), keyword()) ::
  {:ok, binary()} | {:error, Exception.t()}
```

Returns a one-use direct-upload URL for `key`.

# `upload_url!`

Like `upload_url/3`, but returns the URL directly and raises on failure.

# `url`

```elixir
@spec url(Magpie.Client.t(), binary(), keyword()) ::
  {:ok, binary()} | {:error, Exception.t()}
```

Returns a temporary direct-download URL for `key`.

# `url!`

Like `url/3`, but returns the URL directly and raises on failure.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
