1 |
|
-module(mongoose_lib). |
2 |
|
|
3 |
|
-export([log_if_backend_error/4]). |
4 |
|
%% Maps |
5 |
|
-export([maps_append/3]). |
6 |
|
-export([maps_foreach/2]). |
7 |
|
-export([pairs_foreach/2]). |
8 |
|
-export([maps_or_pairs_foreach/2]). |
9 |
|
%% Busy Wait |
10 |
|
-export([wait_until/2, wait_until/3]). |
11 |
|
-export([parse_ip_netmask/1]). |
12 |
|
|
13 |
|
-export([get_message_type/1, does_local_user_exist/3]). |
14 |
|
|
15 |
|
%% Private, just for warning |
16 |
|
-export([deprecated_logging/1]). |
17 |
|
-deprecated({deprecated_logging, 1, eventually}). |
18 |
|
|
19 |
|
-ignore_xref([pairs_foreach/2, wait_until/3]). |
20 |
|
|
21 |
|
-export_type([microseconds/0]). |
22 |
|
-export([pmap/2, pmap/3]). |
23 |
|
-ignore_xref([pmap/3]). |
24 |
|
|
25 |
|
-export([is_exported/3]). |
26 |
|
|
27 |
|
-include("mongoose.hrl"). |
28 |
|
-include("jlib.hrl"). |
29 |
|
|
30 |
|
-type microseconds() :: integer(). |
31 |
|
-type message_type() :: one2one | groupchat. |
32 |
|
|
33 |
|
%% ------------------------------------------------------------------ |
34 |
|
%% Logging |
35 |
|
%% ------------------------------------------------------------------ |
36 |
|
|
37 |
|
%% @doc Database backends for various modules return ok, {atomic, ok} |
38 |
|
%% or {atomic, []} on success, and usually {error, ...} on failure. |
39 |
|
%% All we need is to log an error if such occurred, and proceed normally. |
40 |
|
-spec log_if_backend_error(V :: any(), % value return by called backend fun |
41 |
|
Module :: atom(), % caller |
42 |
|
Line :: integer(), |
43 |
|
Args :: any() ) -> ok. |
44 |
23 |
log_if_backend_error(ok, _Module, _Line, _Args) -> ok; |
45 |
12 |
log_if_backend_error({ok, _}, _Module, _Line, _Args) -> ok; |
46 |
151 |
log_if_backend_error({atomic, _}, _Module, _Line, _Args) -> ok; |
47 |
:-( |
log_if_backend_error({updated, _}, _Module, _Line, _Args) -> ok; |
48 |
|
log_if_backend_error({error, E}, Module, Line, Args) -> |
49 |
:-( |
?LOG_ERROR(#{what => backend_error, |
50 |
|
text => <<"Error calling backend module">>, |
51 |
|
caller_module => Module, caller_line => Line, |
52 |
:-( |
reason => E, args => Args}), |
53 |
:-( |
ok; |
54 |
|
log_if_backend_error(E, Module, Line, Args) -> |
55 |
:-( |
?LOG_ERROR(#{what => backend_error, |
56 |
|
text => <<"Unexpected return from backend">>, |
57 |
|
caller_module => Module, caller_line => Line, |
58 |
:-( |
reason => E, args => Args}), |
59 |
:-( |
ok. |
60 |
|
|
61 |
|
%% ------------------------------------------------------------------ |
62 |
|
%% Maps |
63 |
|
%% ------------------------------------------------------------------ |
64 |
|
|
65 |
|
%% Appends a new Value to the current list of values associated with Key. |
66 |
|
maps_append(Key, Value, Map) -> |
67 |
274 |
Values = maps:get(Key, Map, []), |
68 |
274 |
maps:put(Key, Values ++ [Value], Map). |
69 |
|
|
70 |
|
-spec maps_foreach(fun(), map()) -> ok. |
71 |
|
maps_foreach(Fun, Map) when is_function(Fun, 1) -> |
72 |
275 |
maps:fold(fun(Key, Value, Acc) -> |
73 |
408 |
Fun({Key, Value}), Acc |
74 |
|
end, ok, Map); |
75 |
|
maps_foreach(Fun, Map) when is_function(Fun, 2) -> |
76 |
874 |
maps:fold(fun(Key, Value, Acc) -> |
77 |
1010 |
Fun(Key, Value), Acc |
78 |
|
end, ok, Map). |
79 |
|
|
80 |
|
-spec pairs_foreach(Fun, [{Key, Value}]) -> ok |
81 |
|
when |
82 |
|
Fun :: fun((Key, Value) -> term()) |
83 |
|
| fun(({Key, Value}) -> term()), |
84 |
|
Key :: term(), |
85 |
|
Value :: term(). |
86 |
|
pairs_foreach(Fun, List) when is_function(Fun, 1) -> |
87 |
:-( |
lists:foreach(Fun, List); |
88 |
|
pairs_foreach(Fun, List) when is_function(Fun, 2) -> |
89 |
4 |
lists:foreach(fun({K,V}) -> Fun(K,V) end, List). |
90 |
|
|
91 |
|
maps_or_pairs_foreach(Fun, Map) when is_map(Map) -> |
92 |
550 |
maps_foreach(Fun, Map); |
93 |
|
maps_or_pairs_foreach(Fun, List) when is_list(List) -> |
94 |
4 |
pairs_foreach(Fun, List). |
95 |
|
|
96 |
|
|
97 |
|
%% Busy wait |
98 |
|
wait_until(Fun, ExpectedValue) -> |
99 |
301 |
wait_until(Fun, ExpectedValue, #{}). |
100 |
|
|
101 |
|
wait_until(Fun, ExpectedValue, Opts) -> |
102 |
301 |
Defaults = #{time_left => timer:seconds(5), sleep_time => 100}, |
103 |
301 |
do_wait_until(Fun, ExpectedValue, maps:merge(Defaults, Opts)). |
104 |
|
|
105 |
|
do_wait_until(_, _, #{time_left := TimeLeft}) when TimeLeft =< 0 -> |
106 |
:-( |
ok; |
107 |
|
do_wait_until(Fun, ExpectedValue, Opts) -> |
108 |
302 |
case Fun() of |
109 |
301 |
ExpectedValue -> {ok, ExpectedValue}; |
110 |
1 |
_OtherValue -> wait_and_continue(Fun, ExpectedValue, Opts) |
111 |
|
end. |
112 |
|
|
113 |
|
wait_and_continue(Fun, ExpectedValue, #{time_left := TimeLeft, sleep_time := SleepTime} = Opts) -> |
114 |
1 |
timer:sleep(SleepTime), |
115 |
1 |
do_wait_until(Fun, ExpectedValue, Opts#{time_left => TimeLeft - SleepTime}). |
116 |
|
|
117 |
|
|
118 |
|
deprecated_logging(Location) -> |
119 |
5 |
Map = #{what => deprecated_logging_macro, |
120 |
|
text => <<"Deprecated logging macro is used in your code">>}, |
121 |
5 |
mongoose_deprecations:log(Location, Map, [{log_level, warning}]). |
122 |
|
|
123 |
|
%% ------------------------------------------------------------------ |
124 |
|
%% Parse IP |
125 |
|
%% ------------------------------------------------------------------ |
126 |
|
parse_ip_netmask(S) -> |
127 |
430 |
case string:tokens(S, "/") of |
128 |
:-( |
[IPStr] -> parse_ip_netmask(IPStr, undefined); |
129 |
430 |
[IPStr, MaskStr] -> parse_ip_netmask(IPStr, MaskStr); |
130 |
:-( |
_ -> error |
131 |
|
end. |
132 |
|
|
133 |
|
parse_ip_netmask(IPStr, undefined) -> |
134 |
:-( |
case inet_parse:address(IPStr) of |
135 |
|
{ok, {_, _, _, _} = IP} -> |
136 |
:-( |
{ok, {IP, 32}}; |
137 |
|
{ok, {_, _, _, _, _, _, _, _} = IP} -> |
138 |
:-( |
{ok, {IP, 128}}; |
139 |
|
_ -> |
140 |
:-( |
error |
141 |
|
end; |
142 |
|
parse_ip_netmask(IPStr, MaskStr) -> |
143 |
430 |
case catch list_to_integer(MaskStr) of |
144 |
|
Mask when is_integer(Mask), |
145 |
|
Mask >= 0 -> |
146 |
430 |
case inet_parse:address(IPStr) of |
147 |
|
{ok, {_, _, _, _} = IP} when Mask =< 32 -> |
148 |
430 |
{ok, {IP, Mask}}; |
149 |
|
{ok, {_, _, _, _, _, _, _, _} = IP} when Mask =< 128 -> |
150 |
:-( |
{ok, {IP, Mask}}; |
151 |
|
_ -> |
152 |
:-( |
error |
153 |
|
end; |
154 |
|
_ -> |
155 |
:-( |
error |
156 |
|
end. |
157 |
|
|
158 |
|
%% ------------------------------------------------------------------ |
159 |
|
%% does_local_user_exist |
160 |
|
%% ------------------------------------------------------------------ |
161 |
|
-spec get_message_type(mongoose_acc:t()) -> message_type(). |
162 |
|
get_message_type(Acc) -> |
163 |
:-( |
case mongoose_acc:stanza_type(Acc) of |
164 |
:-( |
<<"groupchat">> -> groupchat; |
165 |
:-( |
_ -> one2one |
166 |
|
end. |
167 |
|
|
168 |
|
-spec does_local_user_exist(mongooseim:host_type(), jid:jid(), message_type()) -> boolean(). |
169 |
|
does_local_user_exist(HostType, To, groupchat) -> |
170 |
:-( |
(not is_to_room(To)) andalso ejabberd_auth:does_user_exist(HostType, To, stored); |
171 |
|
does_local_user_exist(HostType, To, _) -> |
172 |
:-( |
ejabberd_auth:does_user_exist(HostType, To, stored). |
173 |
|
|
174 |
|
%% WHY: filter_local_packet is executed twice in the pipeline of muc messages. in two routing steps: |
175 |
|
%% - From the sender to the room: runs filter_local_packet with From=Sender, To=Room |
176 |
|
%% - For each member of the room: |
177 |
|
%% From the room to each member: runs with From=Room/Sender, To=Member |
178 |
|
%% So, as inbox is a per-user concept, it is on the second routing step only when we want to do act. |
179 |
|
%% NOTE: ideally for groupchats, we could instead act on `filter_room_packet`, like MAM. |
180 |
|
-spec is_to_room(jid:jid()) -> boolean(). |
181 |
|
is_to_room(Jid) -> |
182 |
:-( |
{error, not_found} =:= mongoose_domain_api:get_domain_host_type(Jid#jid.lserver). |
183 |
|
|
184 |
|
%% ------------------------------------------------------------------ |
185 |
|
%% parallel map |
186 |
|
%% ------------------------------------------------------------------ |
187 |
|
|
188 |
|
%% Runs a function for each element on the same node |
189 |
|
pmap(F, Es) -> |
190 |
33 |
pmap(F, Es, 5000). |
191 |
|
|
192 |
|
pmap(F, Es, Timeout) -> |
193 |
33 |
TimerRef = erlang:start_timer(Timeout, self(), pmap_timeout), |
194 |
33 |
Running = |
195 |
52 |
[spawn_monitor(fun() -> exit({pmap_result, F(E)}) end) |
196 |
33 |
|| E <- Es], |
197 |
33 |
Result = collect(Running, TimerRef), |
198 |
33 |
cancel_and_flush_timer(TimerRef), |
199 |
33 |
Result. |
200 |
|
|
201 |
33 |
collect([], _TimerRef) -> []; |
202 |
|
collect([{Pid, MRef} | Next] = In, TimerRef) -> |
203 |
52 |
receive |
204 |
|
{'DOWN', MRef, process, Pid, Reason} -> |
205 |
52 |
[reason_to_result(Reason) | collect(Next, TimerRef)]; |
206 |
|
{timeout, TimerRef, pmap_timeout} -> |
207 |
:-( |
stop_processes(In), |
208 |
:-( |
collect(In, TimerRef) |
209 |
|
end. |
210 |
|
|
211 |
|
stop_processes(In) -> |
212 |
:-( |
[erlang:exit(Pid, timeout) || {Pid, _} <- In]. |
213 |
|
|
214 |
|
reason_to_result({pmap_result, Result}) -> |
215 |
52 |
{ok, Result}; |
216 |
|
reason_to_result(Reason) -> |
217 |
:-( |
{error, Reason}. |
218 |
|
|
219 |
|
cancel_and_flush_timer(TimerRef) -> |
220 |
33 |
erlang:cancel_timer(TimerRef), |
221 |
33 |
receive |
222 |
:-( |
{timeout, TimerRef, _} -> ok |
223 |
33 |
after 0 -> ok |
224 |
|
end. |
225 |
|
|
226 |
|
-spec is_exported(Module :: module(), Function :: atom(), |
227 |
|
Arity :: integer()) -> boolean(). |
228 |
|
is_exported(Module, Function, Arity) -> |
229 |
53674 |
code:ensure_loaded(Module), |
230 |
53674 |
erlang:function_exported(Module, Function, Arity). |