A file that came with a message: a photograph, a PDF, an invitation, a forwarded email.
Nothing of it is decoded until the app asks: read/1 gives its bytes,
for a small file; save/3 writes it to a file as it is decoded, with
its size and checksum; store/2 keeps it in the app's store with
Storehouse, checked by its bytes, under the app's limits:
for attachment <- message.attachments, String.starts_with?(attachment.content_type, "image/") do
Letterbox.Attachment.store(attachment,
accept: Storehouse.Check.images(),
max_bytes: 50_000_000,
owner: "sites/#{site.id}",
room: Media.room(site),
existing: &Media.by_checksum(site, &1)
)
end:inline? is true for a file that the message's HTML shows in its text
(a cid: image): a logo in a signature, but also, from a phone, the
photographs someone put in the body. Whether to keep those is the app's
choice.
Summary
Functions
Its bytes, decoded, in memory: for a small file.
Writes it to path as it is decoded, a chunk at a time. Gives {:ok, %{bytes: …, checksum: …}}, its size and its SHA-256 (in Base64, as
Storehouse gives checksums), or {:error, :too_large} past
:max_bytes, with nothing left at path.
Keeps it in the app's store: Storehouse.store/2 of its bytes, with its
name and claimed type, and the options given (:accept, :max_bytes,
:owner, :room, :existing, :adapter, …). It is decoded to a
temporary file first, which goes once it is stored.
Its bytes, decoded, as a stream of chunks (Letterbox.MIME.stream/2).
Types
@type t() :: %Letterbox.Attachment{ content_id: String.t() | nil, content_type: String.t(), encoded_bytes: non_neg_integer(), inline?: boolean(), name: String.t() | nil, part: Letterbox.MIME.t() }
An attachment:
:name- its file name, as the sender's mail client gave it, decoded; nil when it has none.:content_type- the type the email claims, in lowercase. Only its bytes say what it is (store/2checks them).:content_id- theContent-IDthe HTML'scid:points to, or nil.:inline?- whether the HTML shows it in the text.:encoded_bytes- the size of its body in the email, before it is decoded: about a third more than the file, in Base64.:part- itsLetterbox.MIMEpart.
Functions
Its bytes, decoded, in memory: for a small file.
@spec save(t(), Path.t(), keyword()) :: {:ok, %{bytes: non_neg_integer(), checksum: String.t()}} | {:error, :too_large | File.posix()}
Writes it to path as it is decoded, a chunk at a time. Gives {:ok, %{bytes: …, checksum: …}}, its size and its SHA-256 (in Base64, as
Storehouse gives checksums), or {:error, :too_large} past
:max_bytes, with nothing left at path.
@spec store(t(), keyword()) :: {:ok | :existing, Storehouse.File.t()} | {:error, term()}
Keeps it in the app's store: Storehouse.store/2 of its bytes, with its
name and claimed type, and the options given (:accept, :max_bytes,
:owner, :room, :existing, :adapter, …). It is decoded to a
temporary file first, which goes once it is stored.
Gives what Storehouse.store/2 gives: {:ok, file}, {:existing, file} when the app has the same file already (:existing), or {:error, reason}: :not_accepted, :too_large, :storage_full, …
@spec stream(t(), keyword()) :: Enumerable.t()
Its bytes, decoded, as a stream of chunks (Letterbox.MIME.stream/2).