# `PhoenixKitPhotos.Zip`
[🔗](https://github.com/BeamLabEU/phoenix_kit_photos/blob/v0.3.0/lib/phoenix_kit_photos/zip.ex#L1)

A ZIP archive written front to back, for answering a download while the files are still being
read: nothing is ever seeked back to, so it goes straight into a chunked response.

Files are stored, not compressed: photos and videos are compressed already, and deflating them
costs a lot of CPU to save almost nothing. ZIP64 records appear only when a size or an offset
needs them, so an ordinary archive opens in everything, and one of 40 GB still works.

Each file is measured before it is written (`add/5`), which is why the headers can carry the
real sizes and need no data descriptors. That is also what makes a file that cannot be read
harmless: `{:error, {:read, reason}}` means nothing of it was written and the archive can go
on. `{:error, {:write, reason}}` means it cannot: stop, and do not `finish/1`, so what the
reader has is plainly broken rather than quietly short.

# `t`

```elixir
@type t() :: %PhoenixKitPhotos.Zip{entries: list(), offset: non_neg_integer()}
```

# `add`

```elixir
@spec add(t(), String.t(), Path.t(), DateTime.t() | nil, (iodata() -&gt;
                                                      :ok | {:error, term()})) ::
  {:ok, t()} | {:error, {:read | :write, term()}}
```

Adds the file at `path` as `name`, sending the bytes through `write`, a function of iodata that
answers `:ok` or `{:error, reason}`. `time` is the file's date, a `DateTime` or nil.

# `add_text`

```elixir
@spec add_text(t(), String.t(), iodata(), (iodata() -&gt; :ok | {:error, term()})) ::
  {:ok, t()} | {:error, {:write, term()}}
```

Adds a small text as a file, for a note about the archive itself.

# `finish`

```elixir
@spec finish(t()) :: iodata()
```

The part of the archive that comes after the files.

# `new`

```elixir
@spec new() :: t()
```

An empty archive.

---

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