+2008-12-08 Christophe Romain <christophe.romain@process-one.net>
+
+ * src/ejabberd_c2s.erl: Reduce memory consumption due to caps handling
+ * src/mod_pubsub/mod_pubsub.erl: Likewise
+ * src/mod_caps.erl: Likewise
+
2008-12-08 Mickael Remond <mremond@process-one.net>
* src/ejabberd_c2s.erl: Enforce client stanza from attribute
send_element/2,
socket_type/0,
get_presence/1,
- get_subscribed/1,
- get_subscribed_and_online/1]).
+ get_subscribed/1]).
%% gen_fsm callbacks
-export([init/1,
pres_f = ?SETS:new(),
pres_a = ?SETS:new(),
pres_i = ?SETS:new(),
- pres_available = ?DICT:new(),
pres_last, pres_pri,
pres_timestamp,
pres_invis = false,
end.
%% Return list of all available resources of contacts,
-%% in form [{JID, Caps}].
get_subscribed(FsmRef) ->
- gen_fsm:sync_send_all_state_event(
- FsmRef, get_subscribed, 1000).
-get_subscribed_and_online(FsmRef) ->
- gen_fsm:sync_send_all_state_event(
- FsmRef, get_subscribed_and_online, 1000).
-
+ gen_fsm:sync_send_all_state_event(FsmRef, get_subscribed, 1000).
%%----------------------------------------------------------------------
%% Func: StateName/2
fsm_reply(Reply, StateName, StateData);
handle_sync_event(get_subscribed, _From, StateName, StateData) ->
- Subscribed = StateData#state.pres_f,
- Online = StateData#state.pres_available,
- Pred = fun(User, _Caps) ->
- ?SETS:is_element(jlib:jid_remove_resource(User),
- Subscribed) orelse
- ?SETS:is_element(User, Subscribed)
- end,
- SubscribedAndOnline = ?DICT:filter(Pred, Online),
- SubscribedWithCaps = ?SETS:fold(fun(User, Acc) ->
- [{User, undefined}|Acc]
- end, ?DICT:to_list(SubscribedAndOnline), Subscribed),
- {reply, SubscribedWithCaps, StateName, StateData};
-
-handle_sync_event(get_subscribed_and_online, _From, StateName, StateData) ->
- Subscribed = StateData#state.pres_f,
- Online = StateData#state.pres_available,
- Pred = fun(User, _Caps) ->
- ?SETS:is_element(jlib:jid_remove_resource(User),
- Subscribed) orelse
- ?SETS:is_element(User, Subscribed)
- end,
- SubscribedAndOnline = ?DICT:filter(Pred, Online),
- {reply, ?DICT:to_list(SubscribedAndOnline), StateName, StateData};
+ Subscribed = ?SETS:to_list(StateData#state.pres_f),
+ {reply, Subscribed, StateName, StateData};
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok,
LFrom = jlib:jid_tolower(From),
LBFrom = jlib:jid_remove_resource(LFrom),
%% Note contact availability
- Caps = mod_caps:read_caps(Els),
- mod_caps:note_caps(StateData#state.server, From, Caps),
- NewAvailable = case xml:get_attr_s("type", Attrs) of
- "unavailable" ->
- ?DICT:erase(LFrom, StateData#state.pres_available);
- _ ->
- ?DICT:store(LFrom, Caps, StateData#state.pres_available)
- end,
- NewStateData = StateData#state{pres_available = NewAvailable},
+ case xml:get_attr_s("type", Attrs) of
+ "unavailable" ->
+ mod_caps:clear_caps(From);
+ _ ->
+ Caps = mod_caps:read_caps(Els),
+ mod_caps:note_caps(StateData#state.server, From, Caps)
+ end,
case ?SETS:is_element(
- LFrom, NewStateData#state.pres_a) orelse
+ LFrom, StateData#state.pres_a) orelse
?SETS:is_element(
- LBFrom, NewStateData#state.pres_a) of
+ LBFrom, StateData#state.pres_a) of
true ->
- {true, Attrs, NewStateData};
+ {true, Attrs, StateData};
false ->
case ?SETS:is_element(
- LFrom, NewStateData#state.pres_f) of
+ LFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LFrom,
- NewStateData#state.pres_a),
+ StateData#state.pres_a),
{true, Attrs,
- NewStateData#state{pres_a = A}};
+ StateData#state{pres_a = A}};
false ->
case ?SETS:is_element(
- LBFrom, NewStateData#state.pres_f) of
+ LBFrom, StateData#state.pres_f) of
true ->
A = ?SETS:add_element(
LBFrom,
- NewStateData#state.pres_a),
+ StateData#state.pres_a),
{true, Attrs,
- NewStateData#state{pres_a = A}};
+ StateData#state{pres_a = A}};
false ->
- {true, Attrs, NewStateData}
+ {true, Attrs, StateData}
end
end
end;
-behaviour(gen_mod).
-export([read_caps/1,
+ get_caps/1,
note_caps/3,
+ clear_caps/1,
get_features/2,
handle_disco_response/3]).
-record(caps, {node, version, exts}).
-record(caps_features, {node_pair, features}).
+-record(user_caps, {jid, caps}).
-record(state, {host,
disco_requests = ?DICT:new(),
feature_queries = []}).
read_caps([], Result) ->
Result.
+%% get_caps reads user caps from database
+get_caps(JID) ->
+ case catch mnesia:dirty_read({user_caps, list_to_binary(jlib:jid_to_string(JID))}) of
+ [#user_caps{caps=Caps}] ->
+ Caps;
+ _ ->
+ nothing
+ end.
+
+%% clear_caps removes user caps from database
+clear_caps(JID) ->
+ catch mnesia:dirty_delete({user_caps, list_to_binary(jlib:jid_to_string(JID))}).
+
%% note_caps should be called to make the module request disco
%% information. Host is the host that asks, From is the full JID that
%% sent the caps packet, and Caps is what read_caps returned.
note_caps(Host, From, Caps) ->
case Caps of
- nothing -> ok;
+ nothing ->
+ ok;
_ ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:cast(Proc, {note_caps, From, Caps})
mnesia:create_table(caps_features,
[{ram_copies, [node()]},
{attributes, record_info(fields, caps_features)}]),
- mnesia:add_table_copy(caps_features, node(), ram_copies),
+ mnesia:create_table(user_caps,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, user_caps)}]),
{ok, #state{host = Host}}.
maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
{stop, normal, ok, State}.
handle_cast({note_caps, From,
- #caps{node = Node, version = Version, exts = Exts}},
+ #caps{node = Node, version = Version, exts = Exts} = Caps},
#state{host = Host, disco_requests = Requests} = State) ->
%% XXX: this leads to race conditions where ejabberd will send
%% lots of caps disco requests.
+ mnesia:dirty_write(#user_caps{jid = list_to_binary(jlib:jid_to_string(From)),
+ caps = Caps}),
SubNodes = [Version | Exts],
%% Now, find which of these are not already in the database.
Fun = fun() ->
end,
case mnesia:transaction(Fun) of
{atomic, Missing} ->
- %% For each unknown caps "subnode", we send a disco
- %% request.
- NewRequests =
- lists:foldl(
- fun(SubNode, Dict) ->
+ %% For each unknown caps "subnode", we send a disco request.
+ NewRequests = lists:foldl(
+ fun(SubNode, Dict) ->
ID = randoms:get_string(),
Stanza =
{xmlelement, "iq",
end, State#state.plugins),
%% and send to From last PEP events published by its contacts
case catch ejabberd_c2s:get_subscribed(Pid) of
- ContactsWithCaps when is_list(ContactsWithCaps) ->
- Caps = proplists:get_value(LJID, ContactsWithCaps),
- ContactsUsers = lists:usort(lists:map(
- fun({{User, Server, _}, _}) -> {User, Server} end, ContactsWithCaps)),
+ Contacts when is_list(Contacts) ->
lists:foreach(
- fun({User, Server}) ->
- PepKey = {User, Server, ""},
+ fun({User, Server, _}) ->
+ Owner = {User, Server, ""},
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, options = Options}) ->
case get_option(Options, send_last_published_item) of
on_sub_and_presence ->
- case is_caps_notify(ServerHost, Node, Caps) of
+ case is_caps_notify(ServerHost, Node, LJID) of
true ->
Subscribed = case get_option(Options, access_model) of
open -> true;
element(2, get_roster_info(User, Server, LJID, Grps))
end,
if Subscribed ->
- ?DEBUG("send ~s's ~s event to ~s",[jlib:jid_to_string(PepKey),Node,jlib:jid_to_string(LJID)]),
- send_last_item(PepKey, Node, LJID);
+ send_last_item(Owner, Node, LJID);
true ->
ok
end;
_ ->
ok
end
- end, tree_action(Host, get_nodes, [PepKey]))
- end, ContactsUsers);
+ end, tree_action(Host, get_nodes, [Owner]))
+ end, Contacts);
_ ->
ok
end,
%% broadcast Stanza to all contacts of the user that are advertising
%% interest in this kind of Node.
broadcast_by_caps({LUser, LServer, LResource}, Node, _Type, Stanza) ->
- ?DEBUG("looking for pid of ~p@~p/~p", [LUser, LServer, LResource]),
- %% We need to know the resource, so we can ask for presence data.
- SenderResource = case LResource of
- "" ->
- %% If we don't know the resource, just pick one.
- case ejabberd_sm:get_user_resources(LUser, LServer) of
- [R|_] ->
- R;
- [] ->
- ""
- end;
- _ ->
- LResource
- end,
- case SenderResource of
- "" ->
- ?DEBUG("~p@~p is offline; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
- ok;
- _ ->
- case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
- C2SPid when is_pid(C2SPid) ->
- %% set the from address on the notification to the bare JID of the account owner
- %% Also, add "replyto" if entity has presence subscription to the account owner
- %% See XEP-0163 1.1 section 4.3.1
- Sender = jlib:make_jid(LUser, LServer, ""),
- %%ReplyTo = jlib:make_jid(LUser, LServer, SenderResource), % This has to be used
- case catch ejabberd_c2s:get_subscribed_and_online(C2SPid) of
- ContactsWithCaps when is_list(ContactsWithCaps) ->
- ?DEBUG("found contacts with caps: ~p", [ContactsWithCaps]),
- lists:foreach(
- fun({JID, Caps}) ->
- case is_caps_notify(LServer, Node, Caps) of
- true ->
- To = jlib:make_jid(JID),
- ejabberd_router ! {route, Sender, To, Stanza};
- false ->
- ok
+ SenderResource = user_resource(LUser, LServer, LResource),
+ case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
+ C2SPid when is_pid(C2SPid) ->
+ %% set the from address on the notification to the bare JID of the account owner
+ %% Also, add "replyto" if entity has presence subscription to the account owner
+ %% See XEP-0163 1.1 section 4.3.1
+ Sender = jlib:make_jid(LUser, LServer, ""),
+ %%ReplyTo = jlib:make_jid(LUser, LServer, SenderResource), % This has to be used
+ case catch ejabberd_c2s:get_subscribed(C2SPid) of
+ Contacts when is_list(Contacts) ->
+ Online = lists:foldl(fun({U, S, R}, Acc) ->
+ case user_resource(U, S, R) of
+ [] -> Acc;
+ OR -> [{U, S, OR}|Acc]
end
- end, ContactsWithCaps);
- _ ->
- ok
- end,
- ok;
- _ ->
- ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
- ok
- end
+ end, [], Contacts),
+ lists:foreach(fun(LJID) ->
+ case is_caps_notify(LServer, Node, LJID) of
+ true ->
+ ejabberd_router ! {route, Sender, jlib:make_jid(LJID), Stanza};
+ false ->
+ ok
+ end
+ end, Online);
+ _ ->
+ ok
+ end,
+ ok;
+ _ ->
+ ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
+ ok
end;
broadcast_by_caps(_, _, _, _) ->
ok.
-is_caps_notify(Host, Node, Caps) ->
+user_resource(LUser, LServer, []) ->
+ %% If we don't know the resource, just pick first if any
+ case ejabberd_sm:get_user_resources(LUser, LServer) of
+ [R|_] -> R;
+ [] -> []
+ end;
+user_resource(_, _, LResource) ->
+ LResource.
+
+is_caps_notify(Host, Node, LJID) ->
+ Caps = mod_caps:get_caps(LJID),
case catch mod_caps:get_features(Host, Caps) of
Features when is_list(Features) -> lists:member(Node ++ "+notify", Features);
_ -> false