Merge branch 'issue/2069' into 'develop'
[akkoma] / lib / pleroma / uploaders / uploader.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Uploaders.Uploader do
6 import Pleroma.Web.Gettext
7
8 @moduledoc """
9 Defines the contract to put and get an uploaded file to any backend.
10 """
11
12 @doc """
13 Instructs how to get the file from the backend.
14
15 Used by `Pleroma.Web.Plugs.UploadedMedia`.
16 """
17 @type get_method :: {:static_dir, directory :: String.t()} | {:url, url :: String.t()}
18 @callback get_file(file :: String.t()) :: {:ok, get_method()}
19
20 @doc """
21 Put a file to the backend.
22
23 Returns:
24
25 * `:ok` which assumes `{:ok, upload.path}`
26 * `{:ok, spec}` where spec is:
27 * `{:file, filename :: String.t}` to handle reads with `get_file/1` (recommended)
28
29 This allows to correctly proxy or redirect requests to the backend, while allowing to migrate backends without breaking any URL.
30 * `{url, url :: String.t}` to bypass `get_file/2` and use the `url` directly in the activity.
31 * `{:error, String.t}` error information if the file failed to be saved to the backend.
32 * `:wait_callback` will wait for an http post request at `/api/pleroma/upload_callback/:upload_path` and call the uploader's `http_callback/3` method.
33
34 """
35 @type file_spec :: {:file | :url, String.t()}
36 @callback put_file(Pleroma.Upload.t()) ::
37 :ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
38
39 @callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
40
41 @callback http_callback(Plug.Conn.t(), Map.t()) ::
42 {:ok, Plug.Conn.t()}
43 | {:ok, Plug.Conn.t(), file_spec()}
44 | {:error, Plug.Conn.t(), String.t()}
45 @optional_callbacks http_callback: 2
46
47 @spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
48 def put_file(uploader, upload) do
49 case uploader.put_file(upload) do
50 :ok -> {:ok, {:file, upload.path}}
51 :wait_callback -> handle_callback(uploader, upload)
52 {:ok, _} = ok -> ok
53 {:error, _} = error -> error
54 end
55 end
56
57 defp handle_callback(uploader, upload) do
58 :global.register_name({__MODULE__, upload.path}, self())
59
60 receive do
61 {__MODULE__, pid, conn, params} ->
62 case uploader.http_callback(conn, params) do
63 {:ok, conn, ok} ->
64 send(pid, {__MODULE__, conn})
65 {:ok, ok}
66
67 {:error, conn, error} ->
68 send(pid, {__MODULE__, conn})
69 {:error, error}
70 end
71 after
72 callback_timeout() -> {:error, dgettext("errors", "Uploader callback timeout")}
73 end
74 end
75
76 defp callback_timeout do
77 case Mix.env() do
78 :test -> 1_000
79 _ -> 30_000
80 end
81 end
82 end