total rebase
[anni] / lib / pleroma / web / rich_media / backfill.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.RichMedia.Backfill do
6   alias Pleroma.Web.RichMedia.Card
7   alias Pleroma.Web.RichMedia.Parser
8   alias Pleroma.Web.RichMedia.Parser.TTL
9   alias Pleroma.Workers.RichMediaExpirationWorker
10
11   require Logger
12
13   @backfiller Pleroma.Config.get([__MODULE__, :provider], Pleroma.Web.RichMedia.Backfill.Task)
14   @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
15   @max_attempts 3
16   @retry 5_000
17
18   def start(%{url: url} = args) when is_binary(url) do
19     url_hash = Card.url_to_hash(url)
20
21     args =
22       args
23       |> Map.put(:attempt, 1)
24       |> Map.put(:url_hash, url_hash)
25
26     @backfiller.run(args)
27   end
28
29   def run(%{url: url, url_hash: url_hash, attempt: attempt} = args)
30       when attempt <= @max_attempts do
31     case Parser.parse(url) do
32       {:ok, fields} ->
33         {:ok, card} = Card.create(url, fields)
34
35         maybe_schedule_expiration(url, fields)
36
37         if Map.has_key?(args, :activity_id) do
38           stream_update(args)
39         end
40
41         warm_cache(url_hash, card)
42
43       {:error, {:invalid_metadata, fields}} ->
44         Logger.debug("Rich media incomplete or invalid metadata for #{url}: #{inspect(fields)}")
45         negative_cache(url_hash)
46
47       {:error, :body_too_large} ->
48         Logger.error("Rich media error for #{url}: :body_too_large")
49         negative_cache(url_hash)
50
51       {:error, {:content_type, type}} ->
52         Logger.debug("Rich media error for #{url}: :content_type is #{type}")
53         negative_cache(url_hash)
54
55       e ->
56         Logger.debug("Rich media error for #{url}: #{inspect(e)}")
57
58         :timer.sleep(@retry * attempt)
59
60         run(%{args | attempt: attempt + 1})
61     end
62   end
63
64   def run(%{url: url, url_hash: url_hash}) do
65     Logger.debug("Rich media failure for #{url}")
66
67     negative_cache(url_hash, :timer.minutes(15))
68   end
69
70   defp maybe_schedule_expiration(url, fields) do
71     case TTL.process(fields, url) do
72       {:ok, ttl} when is_number(ttl) ->
73         timestamp = DateTime.from_unix!(ttl)
74
75         RichMediaExpirationWorker.new(%{"url" => url}, scheduled_at: timestamp)
76         |> Oban.insert()
77
78       _ ->
79         :ok
80     end
81   end
82
83   defp stream_update(%{activity_id: activity_id}) do
84     Pleroma.Activity.get_by_id(activity_id)
85     |> Pleroma.Activity.normalize()
86     |> Pleroma.Web.ActivityPub.ActivityPub.stream_out()
87   end
88
89   defp warm_cache(key, val), do: @cachex.put(:rich_media_cache, key, val)
90   defp negative_cache(key, ttl \\ nil), do: @cachex.put(:rich_media_cache, key, nil, ttl: ttl)
91 end
92
93 defmodule Pleroma.Web.RichMedia.Backfill.Task do
94   alias Pleroma.Web.RichMedia.Backfill
95
96   def run(args) do
97     Task.Supervisor.start_child(Pleroma.TaskSupervisor, Backfill, :run, [args],
98       name: {:global, {:rich_media, args.url_hash}}
99     )
100   end
101 end