A disk is a plain value that describes where files are stored.
Build one with Fil.disk/1 (short for new/1) and pass it around. There's no global configuration, no registry and
no process behind it:
disk = Fil.disk(adapter: Fil.Adapter.Local, root: "priv/storage")The struct holds the adapter module together with the state its Fil.Adapter.init/1 returned, and the plugins
attached with Fil.attach/4 or the :plugins option of new/1. The adapter state may contain
credentials, so %Fil.Disk{} has a custom Inspect implementation that prints only the adapter label:
iex> inspect(Fil.disk(adapter: Fil.Adapter.Local, root: "/tmp/fil"))
"#Fil.Disk<local>"Refs and errors contain the disk too. Logging them is safe, because logs use inspect, but anything that stores the
raw term (:erlang.term_to_binary/1, a crash dump) stores the credentials with it.
Summary
Functions
Returns the adapter module backing disk.
A short, human readable label for the disk's adapter.
Builds a disk. Fil.disk/1 is the same function.
Returns the disk for a source/0: the disk itself, the result of a 0-arity function, or the result of
{module, function, args}.
Returns whether two disks are the same storage, so a path names the same file on both.
Types
Functions
Returns the adapter module backing disk.
iex> Fil.Disk.adapter(Fil.disk(adapter: Fil.Adapter.Local, root: "/tmp/fil"))
Fil.Adapter.Local
A short, human readable label for the disk's adapter.
Used by the Inspect implementations of Fil.Disk and Fil.Ref.
iex> Fil.Disk.label(Fil.disk(adapter: Fil.Adapter.Local, root: "/tmp/fil"))
"local"
Builds a disk. Fil.disk/1 is the same function.
iex> Fil.Disk.new(adapter: Fil.Adapter.Local, root: "/tmp/fil")
#Fil.Disk<local>The adapter validates its options here, once. Invalid options raise ArgumentError when the disk is built instead of
failing on first use.
Options
:adapter(atom/0) - Required. The adapter module. Every option other than:adapter,:pluginsand:signed_urlis passed to the adapter, which validates it against its own schema.:plugins(list of tuple ofatom/0,atom/0,keyword/0values) - Plugins to attach, in order, so the first one is the outermost. Each is a plugin callback as{module, function, opts}, attached under the namemodule(seeFil.attach/4). They're plain data, so the plugins can come from config along with the adapter options:plugins: [{Fil.Plugin.ContentType, :call, default: "text/plain"}]. The default value is[].:signed_url(keyword/0) - Default options ofFil.signed_url/3on this disk. The options of a call replace them one by one, sosigned_url: [expires_in: 86_400, disposition: :attachment]gives every signed URL a day and every download the:attachmentdisposition, andFil.signed_url(disk, path, disposition: :inline)still shows a file inline. A default that doesn't fit the method is left out instead of raising, so a default:dispositiondoesn't breakmethod: :put.Fil.LiveView.external/2passes its own:expires_inand:if_exists, so these defaults don't apply to its upload URLs. The default value is[].:expires_in- How long the URL stays valid, in seconds. At most 7 days (604800), the cap of S3.:disposition- Thecontent-dispositionof downloads. Uploads (method: :put) leave it out.:if_exists- What an upload does if a file is already at the path. Downloads leave it out.
Returns the disk for a source/0: the disk itself, the result of a 0-arity function, or the result of
{module, function, args}.
Integrations such as Fil.Plug take their disk in these forms and call this each time they need it. A function or an
MFA can build the disk from runtime config, where a disk in the options would be fixed at compile time. Where the
options are compiled, as a plug's are, the function has to be a capture with the module name, such as
&MyApp.Storage.uploads/0:
iex> disk = Fil.disk(adapter: Fil.Adapter.Memory)
iex> Fil.Disk.resolve(disk) == disk
true
iex> Fil.Disk.resolve(fn -> disk end) == disk
true
iex> Fil.Disk.resolve({Fil, :disk, [[adapter: Fil.Adapter.Memory]]})
#Fil.Disk<memory>Raises ArgumentError when the function returns something other than a disk, or source is none of the three.
Returns whether two disks are the same storage, so a path names the same file on both.
They are when they have the same adapter and the same address (Fil.Adapter.address/1): the same root on a local
disk, the same bucket, region, endpoint and prefix on S3. Plugins don't count, and neither do credentials or other
options that only change how the storage is reached:
iex> uploads = Fil.disk(adapter: Fil.Adapter.S3, bucket: "uploads", region: "eu-central-1")
iex> rotated = Fil.disk(adapter: Fil.Adapter.S3, bucket: "uploads", region: "eu-central-1", session_token: "new")
iex> Fil.Disk.same_storage?(uploads, rotated)
true
iex> Fil.Disk.same_storage?(uploads, Fil.disk(adapter: Fil.Adapter.S3, bucket: "backups", region: "eu-central-1"))
falseFil.Ecto.Ref uses it to check that a ref fits a field.