# `Phoenix.LiveView.UploadWriter`
[🔗](https://github.com/phoenixframework/phoenix_live_view/blob/v1.2.0/lib/phoenix_live_view/upload_writer.ex#L1)

Provides a behavior for writing uploaded chunks to a final destination.

By default, uploads are written to a temporary file on the server and
consumed by the LiveView by reading the temporary file or copying it to
a durable location. Some use cases require custom handling of the uploaded
chunks, such as streaming a user's upload to another server. In these cases,
we don't want the chunks to be written to disk since we only need to forward
them on.

**Note**: Upload writers run inside the channel uploader process, so
any blocking work will block the channel, and errors will crash the channel process.

Custom implementations of `Phoenix.LiveView.UploadWriter` can be passed to
`allow_upload/3`. To initialize the writer with options, define a 3-arity function
that returns a tuple of `{writer, writer_opts}`. For example imagine
an upload writer that logs the chunk sizes and tracks the total bytes sent by the
client:

    socket
    |> allow_upload(:avatar,
      accept: :any,
      writer: fn _name, _entry, _socket -> {EchoWriter, level: :debug} end
    )

And such an `EchoWriter` could look like this:

    defmodule EchoWriter do
      @behaviour Phoenix.LiveView.UploadWriter

      require Logger

      @impl true
      def init(opts) do
        {:ok, %{total: 0, level: Keyword.fetch!(opts, :level)}}
      end

      @impl true
      def meta(state), do: %{level: state.level}

      @impl true
      def write_chunk(data, state) do
        size = byte_size(data)
        Logger.log(state.level, "received chunk of #{size} bytes")
        {:ok, %{state | total: state.total + size}}
      end

      @impl true
      def close(state, reason) do
        Logger.log(state.level, "closing upload after #{state.total} bytes, #{inspect(reason)}")
        {:ok, state}
      end
    end

When the LiveView consumes the uploaded entry, it will receive the `%{level: ...}`
returned from the meta callback. This allows the writer to keep state as it handles
chunks to be later relayed to the LiveView when consumed.

## Close reasons

The `close/2` callback is called when the upload is complete or cancelled. The following
values can be passed:

  * `:done` - The client sent all expected chunks and the upload is awaiting consumption
  * `:cancel` - The upload was canceled, either by the server or the client navigating away.
  * `{:error, reason}` - The upload was canceled due to an error returned from `write_chunk/2`.
    For example, if `write_chunk/2` returns `{:error, :enoent, state}`, the upload will be cancelled
    and `close/2` will be called with the reason `{:error, :enoent}`.

# `close`

```elixir
@callback close(state :: term(), reason :: :done | :cancel | {:error, term()}) ::
  {:ok, state :: term()} | {:error, term()}
```

# `init`

```elixir
@callback init(opts :: term()) :: {:ok, state :: term()} | {:error, term()}
```

# `meta`

```elixir
@callback meta(state :: term()) :: map()
```

# `write_chunk`

```elixir
@callback write_chunk(data :: binary(), state :: term()) ::
  {:ok, state :: term()} | {:error, reason :: term(), state :: term()}
```

---

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