What the photo uploader asks of the server: who may add to a library, which files
it takes, and storing one finished upload. No LiveView in here; the
PhoenixKitPhotos.Components.PhotoUploader component calls it, and so can anything
else that has a file on disk (an importer, a script).
Who may
can_upload?/2: to a user library, its owner, a manager and a contributor
(Libraries.allows?(role, :upload), so a viewer may not and a trashed library has
no one); to a system library such as Media, an Owner/Admin and a holder of the
media permission. Anything else, and a visitor who is not signed in, may not.
The check is made again before and after storage (store/4), because what
the browser says is not what the server decides. If access is lost during
storage, a newly created row is trashed; a pre-existing duplicate is retained.
What it takes
Pictures and videos (accept/0): JPEG, PNG, GIF, WebP, HEIC/HEIF (an iPhone's),
AVIF, TIFF, BMP, SVG, camera raw (DNG, CR2, CR3, NEF, ARW, ORF, RW2, RAF, PEF…, when
the host has given MIME their types: raw_extensions/0); MP4, MOV (QuickTime), WebM,
3GP. A raw file is kept as it is; core develops its previews with libraw
(PhoenixKit.Modules.Storage.CameraRaw). (Every extension has to have a
known MIME type or LiveView refuses to set the upload up; .m4v and .mkv have none
in the MIME table, and phones record .mov and .mp4.) An SVG can carry a script,
so core never shows one as a page: it is kept as it is and downloads from its link,
and its sizes and previews are PNGs drawn from it by librsvg
(PhoenixKit.Modules.Storage.SvgRaster). Not documents: this is a photo library. The size limit is the site's storage_max_upload_size_mb setting
(default 500), the same one the media browser follows.
Storing
store/4 hashes the file from disk in chunks (PhoenixKit.Utils.FileHash), so a
video costs the same memory as a thumbnail, and hands it to
PhoenixKit.Modules.Storage.store_file_in_buckets/7, which keeps it, dedups it and
queues its renditions. It never raises: the caller is a progress callback.
Summary
Functions
The file extensions the uploader takes, for allow_upload/3's :accept.
Whether scope may add files to the library library_uuid.
What to tell a person about a rejected entry (Phoenix.Component.upload_errors/2).
What to tell a person about a file that did not get stored.
The library, or nil.
The most the panel's concurrency control offers.
How many files the uploader's hook keeps in flight at once when it feeds a drop to LiveView: the panel's concurrency control starts here. The browser's own connection limit (about six over HTTP/1.1) still caps what really transfers at the same time.
The most files one batch may hold.
The largest file, in bytes: the site's storage_max_upload_size_mb setting.
The camera raw extensions the uploader takes: those of
PhoenixKit.Modules.Storage.CameraRaw whose type the host has given MIME, because
LiveView refuses to set an upload up for an extension MIME does not know. A host
turns them on with
Stores the finished upload at path (a LiveView upload entry says what it is
called and what the browser thought it was) in the library, for scope's user.
Types
Functions
@spec accept() :: [String.t()]
The file extensions the uploader takes, for allow_upload/3's :accept.
@spec can_upload?(PhoenixKit.Users.Auth.Scope.t() | nil, term()) :: boolean()
Whether scope may add files to the library library_uuid.
What to tell a person about a rejected entry (Phoenix.Component.upload_errors/2).
What to tell a person about a file that did not get stored.
@spec library(term()) :: PhoenixKit.Modules.Storage.Library.t() | nil
The library, or nil.
@spec max_concurrency_choice() :: pos_integer()
The most the panel's concurrency control offers.
@spec max_concurrent_uploads() :: pos_integer()
How many files the uploader's hook keeps in flight at once when it feeds a drop to LiveView: the panel's concurrency control starts here. The browser's own connection limit (about six over HTTP/1.1) still caps what really transfers at the same time.
@spec max_entries() :: pos_integer()
The most files one batch may hold.
@spec max_file_size() :: pos_integer()
The largest file, in bytes: the site's storage_max_upload_size_mb setting.
@spec raw_extensions() :: [String.t()]
The camera raw extensions the uploader takes: those of
PhoenixKit.Modules.Storage.CameraRaw whose type the host has given MIME, because
LiveView refuses to set an upload up for an extension MIME does not know. A host
turns them on with
config :mime, :types, PhoenixKit.Modules.Storage.CameraRaw.mime_types()written out as a literal map (config cannot call the module), followed by
mix deps.compile mime --force.
@spec store(Path.t(), map(), PhoenixKit.Users.Auth.Scope.t() | nil, term()) :: outcome()
Stores the finished upload at path (a LiveView upload entry says what it is
called and what the browser thought it was) in the library, for scope's user.
{:added, file}— a new file (a duplicate that had been trashed is restored and counts as added: uploading it again means it is wanted){:duplicate, file}— the same bytes are already in this library{:elsewhere, file}— the same bytes are already in another of this person's libraries, which this one cannot show{:error, reason}—:not_allowed,:unsupported_type, or what storage said (:quota_exceededwhen the library's owner has no room left for it)