Storehouse.Live.consume/3 stores the files of a LiveView upload: each
entry that is done, once, checked by its bytes and stored under a new
key; and those the upload refused, cancelled. It gives a result for
each, so that the LiveView shows the file, or says why it is not there.
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:photos, [])
|> allow_upload(:photos,
accept: ~w(.png .jpg .jpeg .gif .webp),
max_file_size: 10_000_000,
max_entries: 10,
auto_upload: true,
progress: &handle_progress/3
)}
end
defp handle_progress(:photos, _entry, socket), do: {:noreply, store_photos(socket)}
# The form's phx-change: a file that fails validation (too large, a type
# that is not accepted) is cancelled here.
def handle_event("validate", _params, socket), do: {:noreply, store_photos(socket)}
defp store_photos(socket) do
{socket, results} =
Storehouse.Live.consume(socket, :photos,
accept: Storehouse.Check.images(),
max_pixels: 50_000_000
)
Enum.reduce(results, socket, fn
{_entry, {:ok, file}}, socket ->
update(socket, :photos, &[file | &1])
{entry, {:error, reason}}, socket ->
put_flash(socket, :error, "#{entry.client_name} was not added: #{inspect(reason)}")
end)
end<form id="photos" phx-change="validate">
<.live_file_input upload={@uploads.photos} />
</form>What is checked
The size limit and the accepted extensions are the upload's
(Phoenix.LiveView.allow_upload/3): the browser checks them first, and
the server again. Then Storehouse reads the first bytes of each file. It
keeps a content type only when the bytes prove it (Storehouse.Check);
the type the browser sends is not trusted. :accept refuses the checked
types you do not take: a page renamed .png is not an image, and gives
{:error, :not_accepted}.
Once each
LiveView drops a consumed entry a message after it is consumed. Two
calls in that time (the progress of the next file, a form change) would
consume it twice, and the second would crash the LiveView: consume/3
keeps the refs it consumed, and leaves those entries alone. Call it from
the progress callback, from the form's change handler, or both.
The results
Each result is {entry, {:ok, file}} or {entry, {:error, reason}}, in
the entries' order: the entry's ref matches what the browser has (a
placeholder of yours for the file, say). The reasons are the upload's
(:too_large, :not_accepted, :too_many_files), the check's
(:not_accepted for :accept, :too_large for :max_pixels) and the
store's own.
Direct and streamed uploads
Storehouse.Live.allow/3, in place of allow_upload/3, sends the files
straight from the browser to the store, when the store takes direct
uploads (Storehouse.S3, and Storehouse.Local in development), or, with
mode: :stream, through the LiveView and on to the store as they come,
with no file on the app's disk: consume/3 gives them stored and
checked. See Direct uploads, and Ecto
for files in records.