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