binarize mod_profile

This commit is contained in:
Christophe Romain 2015-03-13 11:13:21 +01:00
parent e87dee1a00
commit 7870f77392
2 changed files with 126 additions and 124 deletions

View File

@ -5,8 +5,6 @@
Author: Magnus Henoch Author: Magnus Henoch
mailto:henoch@dtek.chalmers.se mailto:henoch@dtek.chalmers.se
xmpp:legoscia@jabber.cd.chalmers.se xmpp:legoscia@jabber.cd.chalmers.se
Requirements: ejabberd 2.x.x
Does NOT work with ejabberd 13 or newer.
This module supports storing and retrieving a profile according to This module supports storing and retrieving a profile according to

View File

@ -54,9 +54,9 @@
%%% </x> %%% </x>
%%% </profile> %%% </profile>
%%% </iq> %%% </iq>
%%% %%%
%%% The server will return only the fields that were requested and the user had defined previously: %%% The server will return only the fields that were requested and the user had defined previously:
%%% %%%
%%% <iq from='user@localhost' type='result' to='user@localhost/Home'> %%% <iq from='user@localhost' type='result' to='user@localhost/Home'>
%%% <profile xmlns='urn:xmpp:tmp:profile'> %%% <profile xmlns='urn:xmpp:tmp:profile'>
%%% <x xmlns='jabber:x:data' type='result'> %%% <x xmlns='jabber:x:data' type='result'>
@ -76,141 +76,143 @@
%%%% Headers %%%% Headers
-module(mod_profile). -module(mod_profile).
-author('henoch@dtek.chalmers.se'). -author('henoch@dtek.chalmers.se').
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, -export([start/2, stop/1, process_sm_iq/3,
process_sm_iq/3, get_sm_features/5, remove_user/2]).
get_sm_features/5,
remove_user/2]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
-include("logger.hrl"). -include("logger.hrl").
-record(profile, {us, fields}). -record(profile, {us, fields}).
-define(NS_PROFILE, "urn:xmpp:tmp:profile"). -define(NS_PROFILE, <<"urn:xmpp:tmp:profile">>).
%%%======================= %%%=======================
%%%% gen_mod %%%% gen_mod
start(Host, Opts) -> start(Host, Opts) ->
mnesia:create_table(profile, [{disc_only_copies, [node()]}, mnesia:create_table(profile,
{attributes, record_info(fields, profile)}]), [{disc_only_copies, [node()]},
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), {attributes, record_info(fields, profile)}]),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PROFILE, gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?MODULE, process_sm_iq, IQDisc). ?NS_PROFILE, ?MODULE, process_sm_iq, IQDisc).
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50), ejabberd_hooks:delete(remove_user, Host, ?MODULE,
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features,50), remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PROFILE). ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_PROFILE).
%%%======================= %%%=======================
%%%% Hooks %%%% Hooks
get_sm_features({error, _} = Acc, _From, _To, _Node, _Lang) -> get_sm_features({error, _} = Acc, _From, _To, _Node,
_Lang) ->
Acc; Acc;
get_sm_features(Acc, _From, _To, Node, _Lang) -> get_sm_features(Acc, _From, _To, Node, _Lang) ->
%% XXX: this will make nonexistent users seem to exist. But
%% mod_adhoc and mod_vcard do that already.
case Node of case Node of
[] -> [] ->
case Acc of case Acc of
{result, Features} -> {result, Features} ->
{result, [?NS_PROFILE | Features]}; {result, [?NS_PROFILE | Features]};
empty -> empty -> {result, [?NS_PROFILE]}
{result, [?NS_PROFILE]} end;
end; _ -> Acc
_ ->
Acc
end. end.
remove_user(User, Server) -> remove_user(User, Server) ->
LUser = jlib:nodeprep(User), LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server), LServer = jlib:nameprep(Server),
US = {LUser, LServer}, US = {LUser, LServer},
F = fun() -> F = fun () -> mnesia:delete({profile, US}) end,
mnesia:delete({profile, US})
end,
mnesia:transaction(F). mnesia:transaction(F).
%%%======================= %%%=======================
%%%% IQ handler %%%% IQ handler
process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) -> process_sm_iq(From, To,
#iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of case Type of
set -> set ->
#jid{luser = LUser, lserver = LServer} = From, #jid{luser = LUser, lserver = LServer} = From,
process_sm_iq_set(LUser, LServer, SubEl, IQ); process_sm_iq_set(LUser, LServer, SubEl, IQ);
get -> get ->
#jid{luser = LUser, lserver = LServer} = To, #jid{luser = LUser, lserver = LServer} = To,
process_sm_iq_get(LUser, LServer, SubEl, IQ) process_sm_iq_get(LUser, LServer, SubEl, IQ)
end. end.
process_sm_iq_set(LUser, LServer, SubEl, IQ) -> process_sm_iq_set(LUser, LServer, SubEl, IQ) ->
case lists:member(LServer, ?MYHOSTS) of case lists:member(LServer, ?MYHOSTS) of
true -> true ->
{xmlelement, _, _, SubSubEls} = SubEl, #xmlel{children = SubSubEls} = SubEl,
ElsList = [El || {xmlelement, Name, _Attrs, _Els} = El ElsList = [El
<- xml:remove_cdata(SubSubEls), || #xmlel{name = Name} = El
Name == "x"], <- xml:remove_cdata(SubSubEls),
case ElsList of Name == <<"x">>],
[XData] -> case ElsList of
case set_profile(LUser, LServer, XData) of [XData] ->
ok -> case set_profile(LUser, LServer, XData) of
IQ#iq{type = result, sub_el = []}; ok -> IQ#iq{type = result, sub_el = []};
{error, Error} -> {error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]} IQ#iq{type = error, sub_el = [SubEl, Error]}
end; end;
_ -> _ ->
IQ#iq{type = error, IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
sub_el = [SubEl, ?ERR_BAD_REQUEST]} end;
end; false ->
false -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end. end.
process_sm_iq_get(LUser, LServer, SubEl, IQ) -> process_sm_iq_get(LUser, LServer, SubEl, IQ) ->
ReqFields = get_requested_fields(SubEl), ReqFields = get_requested_fields(SubEl),
case get_profile(LUser, LServer, ReqFields) of case get_profile(LUser, LServer, ReqFields) of
{ok, Fields} -> {ok, Fields} ->
XEl = {xmlelement, "x", [{"xmlns", "jabber:x:data"}, XEl = #xmlel{name = <<"x">>,
{"type", "result"}], Fields}, attrs =
ProfileEl = {xmlelement, "profile", [{<<"xmlns">>, <<"jabber:x:data">>},
[{"xmlns", ?NS_PROFILE}], [XEl]}, {<<"type">>, <<"result">>}],
IQ#iq{type = result, children = Fields},
sub_el = ProfileEl = #xmlel{name = <<"profile">>,
[ProfileEl] attrs = [{<<"xmlns">>, ?NS_PROFILE}],
}; children = [XEl]},
{error, user_not_found} -> IQ#iq{type = result, sub_el = [ProfileEl]};
IQ#iq{type = error, {error, user_not_found} ->
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}; IQ#iq{type = error,
{error, OtherError} -> sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
?ERROR_MSG("Problem found when getting profile of ~p@~p:~n~p", {error, OtherError} ->
[LUser, LServer, OtherError]), ?ERROR_MSG("Problem found when getting profile of "
IQ#iq{type = error, "~p@~p:~n~p",
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} [LUser, LServer, OtherError]),
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end. end.
%%%======================= %%%=======================
%%%% Set profile %%%% Set profile
set_profile(LUser, LServer, {xmlelement, "x", _Attrs, Els}) -> set_profile(LUser, LServer,
#xmlel{name = <<"x">>, children = Els}) ->
US = {LUser, LServer}, US = {LUser, LServer},
F = fun() -> F = fun () ->
mnesia:write(#profile{us = US, fields = Els}) mnesia:write(#profile{us = US, fields = Els})
end, end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, _} -> {atomic, _} -> ok;
ok; _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end. end.
%%%======================= %%%=======================
@ -218,87 +220,89 @@ set_profile(LUser, LServer, {xmlelement, "x", _Attrs, Els}) ->
get_profile(LUser, LServer, []) -> get_profile(LUser, LServer, []) ->
US = {LUser, LServer}, US = {LUser, LServer},
F = fun() -> F = fun () -> mnesia:read({profile, US}) end,
mnesia:read({profile, US})
end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, [#profile{fields = Fields}]} -> {atomic, [#profile{fields = Fields}]} -> {ok, Fields};
{ok, Fields}; {atomic, []} -> {error, user_not_found};
{atomic, []} -> OtherResult -> {error, OtherResult}
{error, user_not_found};
OtherResult ->
{error, OtherResult}
end; end;
get_profile(LUser, LServer, ReqFields) -> get_profile(LUser, LServer, ReqFields) ->
case get_profile(LUser, LServer, []) of case get_profile(LUser, LServer, []) of
{ok, Fields} -> {ok, Fields} ->
filter_profile_fields(Fields, ReqFields); filter_profile_fields(Fields, ReqFields);
Other -> Other Other -> Other
end. end.
filter_profile_fields(Fields, ReqFields) -> filter_profile_fields(Fields, ReqFields) ->
filter_profile_fields(Fields, ReqFields, []). filter_profile_fields(Fields, ReqFields, []).
%% Probably there are many stored fields and the requested fields are few, filter_profile_fields(StoredFields,
%% so it's optimal to traverse the requested fields instead of stored fields [#xmlel{name = <<"field">>, attrs = Attrs,
filter_profile_fields(StoredFields, [{xmlelement, "field", Attrs, []} | ReqFields], ResFields) -> children = []}
| ReqFields],
ResFields) ->
case lists:keysearch(Attrs, 3, StoredFields) of case lists:keysearch(Attrs, 3, StoredFields) of
{value, StoredField} -> {value, StoredField} ->
filter_profile_fields(StoredFields, ReqFields, [StoredField | ResFields]); filter_profile_fields(StoredFields, ReqFields,
false -> [StoredField | ResFields]);
filter_profile_fields(StoredFields, ReqFields, ResFields) false ->
filter_profile_fields(StoredFields, ReqFields,
ResFields)
end; end;
filter_profile_fields(StoredFields, [_FieldForm | ReqFields], ResFields) -> filter_profile_fields(StoredFields,
filter_profile_fields(StoredFields, ReqFields, ResFields); [_FieldForm | ReqFields], ResFields) ->
filter_profile_fields(StoredFields, ReqFields,
ResFields);
filter_profile_fields(_StoredFields, [], ResFields) -> filter_profile_fields(_StoredFields, [], ResFields) ->
FieldFormType = {xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}], FieldFormType = #xmlel{name = <<"field">>,
[{xmlelement, "value",[], [{xmlcdata, <<"urn:xmpp:tmp:profile">>}]}]}, attrs =
%% NOTE: This list reverse is not necessary, it just ensure the returned fields are in the same order than the request [{<<"var">>, <<"FORM_TYPE">>},
{<<"type">>, <<"hidden">>}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children =
[{xmlcdata,
<<"urn:xmpp:tmp:profile">>}]}]},
{ok, [FieldFormType | lists:reverse(ResFields)]}. {ok, [FieldFormType | lists:reverse(ResFields)]}.
%% TODO: la respuesta a una iq query de fields especificos ha de incluir %% TODO: la respuesta a una iq query de fields especificos ha de incluir
%%%======================= %%%=======================
%%%% Mnesia storage %%%% Mnesia storage
%%%======================= %%%=======================
%%%% PubSub storage %%%% PubSub storage
%%%======================= %%%=======================
%%%% XML processing %%%% XML processing
%% Copied from exmpp_xml.erl, then customized %% Copied from exmpp_xml.erl, then customized
get_requested_fields(SubEl) -> get_requested_fields(SubEl) ->
case xml:get_subtag(SubEl, "x") of case xml:get_subtag(SubEl, <<"x">>) of
false -> []; false -> [];
XEl -> get_elements(XEl, "field") XEl -> get_elements(XEl, <<"field">>)
end. end.
get_elements({xmlelement, "x", _, Children}, Name) -> get_elements(#xmlel{name = <<"x">>,
children = Children},
Name) ->
get_elements2(Children, Name); get_elements2(Children, Name);
get_elements(_, _Name) -> get_elements(_, _Name) -> [].
[].
get_elements2([], _Name) -> get_elements2([], _Name) -> [];
[];
get_elements2(Children, Name) -> get_elements2(Children, Name) ->
lists:filter(filter_by_name(Name), Children). lists:filter(filter_by_name(Name), Children).
filter_by_name(Searched_Name) -> filter_by_name(Searched_Name) ->
fun(XML_Element) -> fun (XML_Element) ->
element_matches(XML_Element, Searched_Name) element_matches(XML_Element, Searched_Name)
end. end.
element_matches({xmlelement, Name, _, _}, Name) -> element_matches(#xmlel{name = Name}, Name) -> true;
true; element_matches(_XML_Element, _Name) -> false.
element_matches(_XML_Element, _Name) ->
false.
%%%================ %%%================
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: %%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: