Merge branch 'fix/debian-install-libmagic-typo' into 'develop'
[akkoma] / lib / pleroma / web / fed_sockets / fed_registry.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.Web.FedSockets.FedRegistry do
6 @moduledoc """
7 The FedRegistry stores the active FedSockets for quick retrieval.
8
9 The storage and retrieval portion of the FedRegistry is done in process through
10 elixir's `Registry` module for speed and its ability to monitor for terminated processes.
11
12 Dropped connections will be caught by `Registry` and deleted. Since the next
13 message will initiate a new connection there is no reason to try and reconnect at that point.
14
15 Normally outside modules should have no need to call or use the FedRegistry themselves.
16 """
17
18 alias Pleroma.Web.FedSockets.FedSocket
19 alias Pleroma.Web.FedSockets.SocketInfo
20
21 require Logger
22
23 @default_rejection_duration 15 * 60 * 1000
24 @rejections :fed_socket_rejections
25
26 @doc """
27 Retrieves a FedSocket from the Registry given it's origin.
28
29 The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
30
31 Will return:
32 * {:ok, fed_socket} for working FedSockets
33 * {:error, :rejected} for origins that have been tried and refused within the rejection duration interval
34 * {:error, some_reason} usually :missing for unknown origins
35 """
36 def get_fed_socket(origin) do
37 case get_registry_data(origin) do
38 {:error, reason} ->
39 {:error, reason}
40
41 {:ok, %{state: :connected} = socket_info} ->
42 {:ok, socket_info}
43 end
44 end
45
46 @doc """
47 Adds a connected FedSocket to the Registry.
48
49 Always returns {:ok, fed_socket}
50 """
51 def add_fed_socket(origin, pid \\ nil) do
52 origin
53 |> SocketInfo.build(pid)
54 |> SocketInfo.connect()
55 |> add_socket_info
56 end
57
58 defp add_socket_info(%{origin: origin, state: :connected} = socket_info) do
59 case Registry.register(FedSockets.Registry, origin, socket_info) do
60 {:ok, _owner} ->
61 clear_prior_rejection(origin)
62 Logger.debug("fedsocket added: #{inspect(origin)}")
63
64 {:ok, socket_info}
65
66 {:error, {:already_registered, _pid}} ->
67 FedSocket.close(socket_info)
68 existing_socket_info = Registry.lookup(FedSockets.Registry, origin)
69
70 {:ok, existing_socket_info}
71
72 _ ->
73 {:error, :error_adding_socket}
74 end
75 end
76
77 @doc """
78 Mark this origin as having rejected a connection attempt.
79 This will keep it from getting additional connection attempts
80 for a period of time specified in the config.
81
82 Always returns {:ok, new_reg_data}
83 """
84 def set_host_rejected(uri) do
85 new_reg_data =
86 uri
87 |> SocketInfo.origin()
88 |> get_or_create_registry_data()
89 |> set_to_rejected()
90 |> save_registry_data()
91
92 {:ok, new_reg_data}
93 end
94
95 @doc """
96 Retrieves the FedRegistryData from the Registry given it's origin.
97
98 The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
99
100 Will return:
101 * {:ok, fed_registry_data} for known origins
102 * {:error, :missing} for uniknown origins
103 * {:error, :cache_error} indicating some low level runtime issues
104 """
105 def get_registry_data(origin) do
106 case Registry.lookup(FedSockets.Registry, origin) do
107 [] ->
108 if is_rejected?(origin) do
109 Logger.debug("previously rejected fedsocket requested")
110 {:error, :rejected}
111 else
112 {:error, :missing}
113 end
114
115 [{_pid, %{state: :connected} = socket_info}] ->
116 {:ok, socket_info}
117
118 _ ->
119 {:error, :cache_error}
120 end
121 end
122
123 @doc """
124 Retrieves a map of all sockets from the Registry. The keys are the origins and the values are the corresponding SocketInfo
125 """
126 def list_all do
127 (list_all_connected() ++ list_all_rejected())
128 |> Enum.into(%{})
129 end
130
131 defp list_all_connected do
132 FedSockets.Registry
133 |> Registry.select([{{:"$1", :_, :"$3"}, [], [{{:"$1", :"$3"}}]}])
134 end
135
136 defp list_all_rejected do
137 {:ok, keys} = Cachex.keys(@rejections)
138
139 {:ok, registry_data} =
140 Cachex.execute(@rejections, fn worker ->
141 Enum.map(keys, fn k -> {k, Cachex.get!(worker, k)} end)
142 end)
143
144 registry_data
145 end
146
147 defp clear_prior_rejection(origin),
148 do: Cachex.del(@rejections, origin)
149
150 defp is_rejected?(origin) do
151 case Cachex.get(@rejections, origin) do
152 {:ok, nil} ->
153 false
154
155 {:ok, _} ->
156 true
157 end
158 end
159
160 defp get_or_create_registry_data(origin) do
161 case get_registry_data(origin) do
162 {:error, :missing} ->
163 %SocketInfo{origin: origin}
164
165 {:ok, socket_info} ->
166 socket_info
167 end
168 end
169
170 defp save_registry_data(%SocketInfo{origin: origin, state: :connected} = socket_info) do
171 {:ok, true} = Registry.update_value(FedSockets.Registry, origin, fn _ -> socket_info end)
172 socket_info
173 end
174
175 defp save_registry_data(%SocketInfo{origin: origin, state: :rejected} = socket_info) do
176 rejection_expiration =
177 Pleroma.Config.get([:fed_sockets, :rejection_duration], @default_rejection_duration)
178
179 {:ok, true} = Cachex.put(@rejections, origin, socket_info, ttl: rejection_expiration)
180 socket_info
181 end
182
183 defp set_to_rejected(%SocketInfo{} = socket_info),
184 do: %SocketInfo{socket_info | state: :rejected}
185 end