# `Fil.Tmp`
[🔗](https://github.com/pehbehbeh/fil/blob/v0.2.0/lib/fil/tmp.ex#L1)

Temporary files and directories, which are removed when the process that created them exits.

`Fil.tmp/0` creates a directory and `Fil.tmp/1` a file name in a directory of its own. Both return a plain `Fil.Ref`
on a local disk rooted at that directory, so the whole API works on it:

    report = Fil.tmp("report.pdf")
    {:ok, _} = Fil.cp(Fil.ref(invoices, "2026/09.pdf"), report)
    {text, 0} = System.cmd("pdftotext", [Fil.Tmp.path(report), "-"])

`path/1` returns the absolute path of a ref on a temporary directory, for tools that need one. A tool can write into
the directory too, and `Fil` lists what it wrote:

    frames = Fil.tmp()
    {_, 0} = System.cmd("ffmpeg", ["-i", Fil.Tmp.path(video), Fil.Tmp.path(frames.disk, "%03d.png")])
    {:ok, pngs} = Fil.ls(frames)

Files in the directory are refs on its disk, such as `Fil.ref(frames.disk, "001.png")`.

## The directory

Each call creates a new directory in `System.tmp_dir!/0` (which checks the `TMPDIR`, `TMP` and `TEMP` environment
variables), named `fil-` and a random suffix. Only the operating system user that runs the node can open it (mode
`0o700`), because a copy from a disk with an encryption plugin is plaintext here. On Windows the mode does nothing.

The name passed to `Fil.tmp/1` is used as it is, so tools, mail attachments and `Fil.Plugin.ContentType` see the real
file name. It can have directories (`"pages/1.png"`), but it has to stay inside the directory: an absolute path, `""`,
`"."` or a name that escapes with `..` raises `ArgumentError`. Pass a name from user input, such as `upload.filename`,
through `Path.basename/1` first.

## Cleanup

The directory belongs to the process that called `Fil.tmp/0,1`. It's removed with everything in it when that process
exits, also when it's killed, by a process of the `:fil` application. So:

  * a temporary file created inside a `Task` is gone when the task returns. Create it in the caller and pass it in, or
    hand it to the caller with `give_away/2`.
  * a long-lived process (a GenServer, a LiveView, a channel) keeps its temporary files for as long as it runs. So
    does a request on Bandit over HTTP/1.1, whose process serves the whole keep-alive connection (see
    [Uploads and temporary files](installation.md#uploads-and-temporary-files)). Call `cleanup/1` once it's done
    with them. A write to a temporary ref after that creates an ordinary directory, with the default mode, which
    `Fil` doesn't remove.
  * `Fil.rm_rf/1` on the directory removes it, but it stays registered to its owner, and a later write creates it
    again without the `0o700` mode. `cleanup/1` removes both.
  * a temporary ref works only on the node that created it.
  * stopping the `:fil` application removes every temporary directory. A crash of the whole node (`kill -9`, a power
    loss) leaves them behind in the system's temporary directory.

`Fil.tmp/0,1` raises when the `:fil` application isn't running, because nothing would remove the directory then.

# `cleanup`

```elixir
@spec cleanup(pid()) :: :ok
```

Removes the temporary directories of `pid` now. Returns `:ok`.

For processes that run long after they're done with their temporary files. It never fails: a directory that can't
be removed completely is left as it is. It leaves the temporary files of `Fil.Adapter.Local` writes in progress
alone, so a write of `pid` still works. Cleaning up another process races with its `give_away/2`: a directory it hands
away meanwhile is removed, and `path/1` then points to nothing.

Don't use the refs afterwards. A write to one creates an ordinary directory in its place, with the default mode,
which `Fil` doesn't remove.

# `give_away`

```elixir
@spec give_away(Fil.Ref.t(), pid()) :: :ok
```

Makes `pid` the owner of a temporary directory, so it's removed when `pid` exits instead of the calling process. The
ref can name the directory or anything in it. Returns `:ok`.

    report = Fil.tmp("report.pdf")
    Fil.write!(report, pdf)
    :ok = Fil.Tmp.give_away(report, caller)

If `pid` is dead already, the directory is removed right away. It raises `ArgumentError` when the calling process
doesn't own the directory, and for a `pid` on another node.

# `new`

```elixir
@spec new() :: Fil.Ref.t()
```

Creates a temporary directory and returns a ref to it, with path `"."`. `Fil.tmp/0` is the same function.

    iex> Fil.Tmp.new()
    #Fil.Ref<local:.>

When the directory can't be created, it raises the error struct `Fil.Adapter.Local` would return, such as a
`Fil.AccessDeniedError` or a `Fil.StorageFullError`, with the directory as `:path`. Without the `:fil` application
running, it raises a `RuntimeError`.

# `new`

```elixir
@spec new(Path.t()) :: Fil.Ref.t()
```

Creates a temporary directory and returns a ref to the file `name` in it. The file doesn't exist until something
writes it. `Fil.tmp/1` is the same function.

    iex> Fil.Tmp.new("pages/1.png")
    #Fil.Ref<local:pages/1.png>

The name has to be a relative path that stays inside the directory (see "The directory" above). Anything else raises
`ArgumentError` before a directory is created. Otherwise it raises like `new/0`.

# `path`

```elixir
@spec path(Fil.Ref.t()) :: Path.t()
```

Returns the absolute path of a ref on a temporary directory: of the directory itself, a file in it or anything else
on its disk.

It raises `ArgumentError` for a ref on another disk, and after `cleanup/1` or the owner's exit.

# `path`

```elixir
@spec path(Fil.Disk.t(), Path.t()) :: Path.t()
```

Returns the absolute path of `path` on a temporary directory's disk. See `path/1`.
