# `Storehouse.Analyzer`
[🔗](https://github.com/hattoriai/storehouse/blob/v0.2.0/lib/storehouse/analyzer.ex#L1)

Work on a file after it is stored: what it is read for (an image's EXIF,
a placeholder, a video's length, a PDF's pages), each step an analyzer,
what it made of the file kept in the file's `:analysis`, by its name, as
a `Storehouse.Analysis`: its status, its result, why it failed.

    defmodule MyApp.Analyzers.Pages do
      @behaviour Storehouse.Analyzer

      @impl true
      def analyze(%Storehouse.File{content_type: "application/pdf"}, path, _opts) do
        {output, 0} = System.cmd("pdfinfo", [path])
        [_, pages] = Regex.run(~r/Pages:\s+(\d+)/, output)
        {:ok, %{"pages" => String.to_integer(pages)}}
      end

      def analyze(_file, _path, _opts), do: :skip
    end

    {:ok, file} = Storehouse.analyze(file, [MyApp.Analyzers.Pages])
    Storehouse.File.analyzed(file, MyApp.Analyzers.Pages.name())
    #=> %{"pages" => 12}

## Steps

A step is an analyzer's module, or `{module, opts}`. They run in their
order, and each is run once: a step whose result the file has already
is skipped (`force: true` runs it again). A step that fails leaves the
others' results, and its own analysis is `:failed`, with why; the next
run tries it again. `:skip` means the step does not apply to the file,
and its analysis is `:skipped`.

The file is read from the store once for all the steps that need it
(`c:needs_file?/0`), to a temporary file, and that file is removed
after them. A file just stored from a path (`Storehouse.store/2` with
`:analyze`) is read from that path, with no download.

## Later

`Storehouse.analyze_later/3` runs them out of the request, as a job of
the app's runner (`Storehouse.Runner`: Oban's, a process of the app's,
or one of its own); then the app's `:then` function gets the file with
its results, to save them. `Storehouse.Ecto.analyze_later/5` saves them
in the record's column.

## Telemetry

Each step is a span, `[:storehouse, :analyze, ...]`, with the `:key` and
the `:step`, and `result: :ok | :skip | :error` (with the `:reason`).

# `step`

```elixir
@type step() :: module() | {module(), keyword()}
```

A step: an analyzer's module, or the module and its options.

# `analyze`

```elixir
@callback analyze(Storehouse.File.t(), path :: Path.t() | nil, opts :: keyword()) ::
  {:ok, map()} | {:ok, map(), Path.t()} | :skip | {:error, term()}
```

Reads what the step reads from the file, at `path` on the local disk (or
`nil`, for a step that does not need the file: `c:needs_file?/0`).
`opts` are the step's, with the store as `:adapter`.
`{:ok, result}` is kept as the step's result, a map JSON can hold
(`Storehouse.Metadata`); `:skip` when the step does not apply to the
file.

A step that changes the stored file (`Storehouse.Analyzer.EditMetadata`)
gives `{:ok, result, path}`, with the file as it is now at `path`, a
temporary file of its own: the steps after it read that one, and it is
removed after them.

# `name`
*optional* 

```elixir
@callback name() :: String.t()
```

The name the step's result is kept under. Optional: the default is the
module's last part, in snake case (`MyApp.Analyzers.Pages` → `"pages"`).

# `needs_file?`
*optional* 

```elixir
@callback needs_file?() :: boolean()
```

Whether the step reads the file's bytes. Optional: the default is `true`.

# `name`

```elixir
@spec name(step()) :: String.t()
```

The name a step's result is kept under (`c:name/0`).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
