25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

* src/mod_caps.erl: CAPS support (thanks to Magnus Henoch)

* src/ejabberd_local.erl: Support for IQ responses
* src/jlib.erl: Added iq_query_or_response_info/1 function
* src/jlib.hrl: Added NS_PUBSUB_ERRORS and NS_CAPS

* src/mod_pubsub/Makefile.in: New pubsub+pep implementation
(thanks to Christophe Romain and Magnus Henoch)
* src/ejabberd_sm.erl: Added get_session_pid/3 function
* src/ejabberd_c2s.erl: Added get_subscribed_and_online/1 function

SVN Revision: 1004
This commit is contained in:
Alexey Shchepin 2007-12-01 05:16:30 +00:00
parent e4cf286aa2
commit c3c782d882
23 changed files with 5233 additions and 1346 deletions

View File

@ -1,32 +1,44 @@
2007-12-01 Alexey Shchepin <alexey@process-one.net>
* src/mod_caps.erl: CAPS support (thanks to Magnus Henoch)
* src/ejabberd_local.erl: Support for IQ responses
* src/jlib.erl: Added iq_query_or_response_info/1 function
* src/jlib.hrl: Added NS_PUBSUB_ERRORS and NS_CAPS
* src/mod_pubsub/Makefile.in: New pubsub+pep implementation
(thanks to Christophe Romain and Magnus Henoch)
* src/ejabberd_sm.erl: Added get_session_pid/3 function
* src/ejabberd_c2s.erl: Added get_subscribed_and_online/1 function
2007-11-30 Mickael Remond <mremond@process-one.net> 2007-11-30 Mickael Remond <mremond@process-one.net>
* src/odbc_queries.erl: Added a default define value so that we can * src/odbc_queries.erl: Added a default define value so that we
recompile the file manually with a simple erlc command. can recompile the file manually with a simple erlc command.
2007-11-29 Badlop <badlop@process-one.net> 2007-11-29 Badlop <badlop@process-one.net>
* src/mod_vcard.erl: Add type of x:data field to search * src/mod_vcard.erl: Add type of x:data field to search results
results (thanks to Robin Redeker) (EJAB-327) (thanks to Robin Redeker) (EJAB-327)
* src/mod_vcard_ldap.erl: * src/mod_vcard_ldap.erl: Likewise
* src/mod_vcard_odbc.erl: * src/mod_vcard_odbc.erl: Likewise
* src/aclocal.m4: Fix autoconf caching for SSL libraries (thanks * src/aclocal.m4: Fix autoconf caching for SSL libraries (thanks
to Michael Shields) (EJAB-439) to Michael Shields) (EJAB-439)
* src/configure.ac: Don't hardcode gcc and gcc options in * src/configure.ac: Don't hardcode gcc and gcc options in
Makefiles (thanks to Etan Reisner) (EJAB-436) Makefiles (thanks to Etan Reisner) (EJAB-436)
* src/Makefile.in: * src/Makefile.in: Likewise
* src/ejabberd_zlib/Makefile.in: * src/ejabberd_zlib/Makefile.in: Likewise
* src/eldap/Makefile.in: * src/eldap/Makefile.in: Likewise
* src/mod_irc/Makefile.in: * src/mod_irc/Makefile.in: Likewise
* src/mod_muc/Makefile.in: * src/mod_muc/Makefile.in: Likewise
* src/mod_proxy65/Makefile.in: * src/mod_proxy65/Makefile.in: Likewise
* src/mod_pubsub/Makefile.in: * src/mod_pubsub/Makefile.in: Likewise
* src/odbc/Makefile.in: * src/odbc/Makefile.in: Likewise
* src/pam/Makefile.in: * src/pam/Makefile.in: Likewise
* src/stringprep/Makefile.in: * src/stringprep/Makefile.in: Likewise
* src/tls/Makefile.in: * src/tls/Makefile.in: Likewise
* src/web/Makefile.in: * src/web/Makefile.in: Likewise
* src/mod_muc/mod_muc_room.erl: Hide the option 'Make room * src/mod_muc/mod_muc_room.erl: Hide the option 'Make room
moderated' because it isn't implemented, and set the default value moderated' because it isn't implemented, and set the default value

View File

@ -18,7 +18,8 @@
send_text/2, send_text/2,
send_element/2, send_element/2,
socket_type/0, socket_type/0,
get_presence/1]). get_presence/1,
get_subscribed_and_online/1]).
%% gen_fsm callbacks %% gen_fsm callbacks
-export([init/1, -export([init/1,
@ -39,6 +40,7 @@
-include("jlib.hrl"). -include("jlib.hrl").
-define(SETS, gb_sets). -define(SETS, gb_sets).
-define(DICT, dict).
-record(state, {socket, -record(state, {socket,
sockmod, sockmod,
@ -60,6 +62,7 @@
pres_f = ?SETS:new(), pres_f = ?SETS:new(),
pres_a = ?SETS:new(), pres_a = ?SETS:new(),
pres_i = ?SETS:new(), pres_i = ?SETS:new(),
pres_available = ?DICT:new(),
pres_last, pres_pri, pres_last, pres_pri,
pres_timestamp, pres_timestamp,
pres_invis = false, pres_invis = false,
@ -173,6 +176,12 @@ init([{SockMod, Socket}, Opts]) ->
shaper = Shaper, shaper = Shaper,
ip = IP}, ?C2S_OPEN_TIMEOUT}. ip = IP}, ?C2S_OPEN_TIMEOUT}.
%% Return list of all available resources of contacts,
%% in form [{JID, Caps}].
get_subscribed_and_online(FsmRef) ->
gen_fsm:sync_send_all_state_event(
FsmRef, get_subscribed_and_online, 1000).
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
%% Func: StateName/2 %% Func: StateName/2
@ -572,7 +581,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
case xml:get_subtag(El, "method") of case xml:get_subtag(El, "method") of
false -> false ->
send_element(StateData, send_element(StateData,
{xmlelement, "failure", {xmlelement, "failure",
[{"xmlns", ?NS_COMPRESS}], [{"xmlns", ?NS_COMPRESS}],
[{xmlelement, "setup-failed", [], []}]}), [{xmlelement, "setup-failed", [], []}]}),
fsm_next_state(wait_for_feature_request, StateData); fsm_next_state(wait_for_feature_request, StateData);
@ -964,6 +973,17 @@ handle_sync_event({get_presence}, _From, StateName, StateData) ->
Reply = {User, Resource, Show, Status}, Reply = {User, Resource, Show, Status},
fsm_reply(Reply, StateName, StateData); fsm_reply(Reply, 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};
handle_sync_event(_Event, _From, StateName, StateData) -> handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok, Reply = ok,
fsm_reply(Reply, StateName, StateData). fsm_reply(Reply, StateName, StateData).
@ -1054,32 +1074,42 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
allow -> allow ->
LFrom = jlib:jid_tolower(From), LFrom = jlib:jid_tolower(From),
LBFrom = jlib:jid_remove_resource(LFrom), 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 ?SETS:is_element( case ?SETS:is_element(
LFrom, StateData#state.pres_a) orelse LFrom, NewStateData#state.pres_a) orelse
?SETS:is_element( ?SETS:is_element(
LBFrom, StateData#state.pres_a) of LBFrom, NewStateData#state.pres_a) of
true -> true ->
{true, Attrs, StateData}; {true, Attrs, NewStateData};
false -> false ->
case ?SETS:is_element( case ?SETS:is_element(
LFrom, StateData#state.pres_f) of LFrom, NewStateData#state.pres_f) of
true -> true ->
A = ?SETS:add_element( A = ?SETS:add_element(
LFrom, LFrom,
StateData#state.pres_a), NewStateData#state.pres_a),
{true, Attrs, {true, Attrs,
StateData#state{pres_a = A}}; NewStateData#state{pres_a = A}};
false -> false ->
case ?SETS:is_element( case ?SETS:is_element(
LBFrom, StateData#state.pres_f) of LBFrom, NewStateData#state.pres_f) of
true -> true ->
A = ?SETS:add_element( A = ?SETS:add_element(
LBFrom, LBFrom,
StateData#state.pres_a), NewStateData#state.pres_a),
{true, Attrs, {true, Attrs,
StateData#state{pres_a = A}}; NewStateData#state{pres_a = A}};
false -> false ->
{true, Attrs, StateData} {true, Attrs, NewStateData}
end end
end end
end; end;

View File

@ -18,6 +18,7 @@
-export([route/3, -export([route/3,
register_iq_handler/4, register_iq_handler/4,
register_iq_handler/5, register_iq_handler/5,
register_iq_response_handler/4,
unregister_iq_handler/2, unregister_iq_handler/2,
refresh_iq_handlers/0, refresh_iq_handlers/0,
bounce_resource_packet/3 bounce_resource_packet/3
@ -32,6 +33,8 @@
-record(state, {}). -record(state, {}).
-record(iq_response, {id, module, function}).
-define(IQTABLE, local_iqtable). -define(IQTABLE, local_iqtable).
%%==================================================================== %%====================================================================
@ -68,13 +71,38 @@ process_iq(From, To, Packet) ->
ejabberd_router:route(To, From, Err) ejabberd_router:route(To, From, Err)
end; end;
reply -> reply ->
ok; process_iq_reply(From, To, Packet);
_ -> _ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
ejabberd_router:route(To, From, Err), ejabberd_router:route(To, From, Err),
ok ok
end. end.
process_iq_reply(From, To, Packet) ->
IQ = jlib:iq_query_or_response_info(Packet),
#iq{id = ID} = IQ,
case catch mnesia:dirty_read(iq_response, ID) of
[] ->
ok;
_ ->
F = fun() ->
case mnesia:read({iq_response, ID}) of
[] ->
nothing;
[#iq_response{module = Module,
function = Function}] ->
mnesia:delete({iq_response, ID}),
{Module, Function}
end
end,
case mnesia:transaction(F) of
{atomic, {Module, Function}} ->
Module:Function(From, To, IQ);
_ ->
ok
end
end.
route(From, To, Packet) -> route(From, To, Packet) ->
case catch do_route(From, To, Packet) of case catch do_route(From, To, Packet) of
{'EXIT', Reason} -> {'EXIT', Reason} ->
@ -84,6 +112,9 @@ route(From, To, Packet) ->
ok ok
end. end.
register_iq_response_handler(Host, ID, Module, Fun) ->
ejabberd_local ! {register_iq_response_handler, Host, ID, Module, Fun}.
register_iq_handler(Host, XMLNS, Module, Fun) -> register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}. ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
@ -120,6 +151,9 @@ init([]) ->
?MODULE, bounce_resource_packet, 100) ?MODULE, bounce_resource_packet, 100)
end, ?MYHOSTS), end, ?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]), catch ets:new(?IQTABLE, [named_table, public]),
mnesia:create_table(iq_response,
[{ram_copies, [node()]},
{attributes, record_info(fields, iq_response)}]),
{ok, #state{}}. {ok, #state{}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -159,6 +193,9 @@ handle_info({route, From, To, Packet}, State) ->
ok ok
end, end,
{noreply, State}; {noreply, State};
handle_info({register_iq_response_handler, _Host, ID, Module, Function}, State) ->
mnesia:dirty_write(#iq_response{id = ID, module = Module, function = Function}),
{noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}), ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
catch mod_disco:register_feature(Host, XMLNS), catch mod_disco:register_feature(Host, XMLNS),

View File

@ -28,6 +28,7 @@
register_iq_handler/5, register_iq_handler/5,
unregister_iq_handler/2, unregister_iq_handler/2,
ctl_process/2, ctl_process/2,
get_session_pid/3,
get_user_ip/3 get_user_ip/3
]). ]).
@ -74,7 +75,7 @@ open_session(SID, User, Server, Resource, IP) ->
close_session(SID, User, Server, Resource) -> close_session(SID, User, Server, Resource) ->
F = fun() -> F = fun() ->
mnesia:delete({session, SID}) mnesia:delete({session, SID})
end, end,
mnesia:sync_dirty(F), mnesia:sync_dirty(F),
JID = jlib:make_jid(User, Server, Resource), JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver, ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
@ -139,6 +140,15 @@ close_session_unset_presence(SID, User, Server, Resource, Status) ->
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server), ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
[User, Server, Resource, Status]). [User, Server, Resource, Status]).
get_session_pid(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
USR = {LUser, LServer, LResource},
case catch mnesia:dirty_index_read(session, USR, #session.usr) of
[#session{sid = {_, Pid}}] -> Pid;
_ -> none
end.
dirty_get_sessions_list() -> dirty_get_sessions_list() ->
mnesia:dirty_select( mnesia:dirty_select(
@ -315,7 +325,7 @@ clean_table_from_bad_node(Node) ->
lists:foreach(fun(E) -> lists:foreach(fun(E) ->
mnesia:delete({session, E#session.sid}) mnesia:delete({session, E#session.sid})
end, Es) end, Es)
end, end,
mnesia:sync_dirty(F). mnesia:sync_dirty(F).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@ -368,7 +378,6 @@ do_route(From, To, Packet) ->
{true, false} {true, false}
end, end,
if Pass -> if Pass ->
LFrom = jlib:jid_tolower(From),
PResources = get_user_present_resources( PResources = get_user_present_resources(
LUser, LServer), LUser, LServer),
lists:foreach( lists:foreach(
@ -377,7 +386,9 @@ do_route(From, To, Packet) ->
From, From,
jlib:jid_replace_resource(To, R), jlib:jid_replace_resource(To, R),
Packet) Packet)
end, PResources); end, PResources),
ejabberd_hooks:run(incoming_presence_hook, LServer,
[From, To, Packet]);
true -> true ->
ok ok
end; end;
@ -649,4 +660,3 @@ update_tables() ->
false -> false ->
ok ok
end. end.

View File

@ -32,6 +32,7 @@
jid_replace_resource/2, jid_replace_resource/2,
get_iq_namespace/1, get_iq_namespace/1,
iq_query_info/1, iq_query_info/1,
iq_query_or_response_info/1,
is_iq_request_type/1, is_iq_request_type/1,
iq_to_xml/1, iq_to_xml/1,
parse_xdata_submit/1, parse_xdata_submit/1,
@ -331,39 +332,66 @@ get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
get_iq_namespace(_) -> get_iq_namespace(_) ->
"". "".
iq_query_info({xmlelement, Name, Attrs, Els}) when Name == "iq" -> iq_query_info(El) ->
iq_info_internal(El, request).
iq_query_or_response_info(El) ->
iq_info_internal(El, any).
iq_info_internal({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
%% Filter is either request or any. If it is request, any replies
%% are converted to the atom reply.
ID = xml:get_attr_s("id", Attrs), ID = xml:get_attr_s("id", Attrs),
Type = xml:get_attr_s("type", Attrs), Type = xml:get_attr_s("type", Attrs),
Lang = xml:get_attr_s("xml:lang", Attrs), Lang = xml:get_attr_s("xml:lang", Attrs),
Type1 = case Type of {Type1, Class} = case Type of
"set" -> set; "set" -> {set, request};
"get" -> get; "get" -> {get, request};
"result" -> reply; "result" -> {result, reply};
"error" -> reply; "error" -> {error, reply};
_ -> invalid _ -> {invalid, invalid}
end, end,
if if
(Type1 /= invalid) and (Type1 /= reply) -> Type1 == invalid ->
case xml:remove_cdata(Els) of invalid;
[{xmlelement, Name2, Attrs2, Els2}] -> Class == request; Filter == any ->
XMLNS = xml:get_attr_s("xmlns", Attrs2), %% The iq record is a bit strange. The sub_el field is an
if %% XML tuple for requests, but a list of XML tuples for
XMLNS /= "" -> %% responses.
FilteredEls = xml:remove_cdata(Els),
{XMLNS, SubEl} =
case {Class, FilteredEls} of
{request, [{xmlelement, _Name2, Attrs2, _Els2}]} ->
{xml:get_attr_s("xmlns", Attrs2),
hd(FilteredEls)};
{reply, _} ->
%% Find the namespace of the first non-error
%% element, if there is one.
NonErrorEls = [El ||
{xmlelement, SubName, _, _} = El
<- FilteredEls,
SubName /= "error"],
{case NonErrorEls of
[NonErrorEl] -> xml:get_tag_attr_s("xmlns", NonErrorEl);
_ -> invalid
end,
FilteredEls};
_ ->
{invalid, invalid}
end,
if XMLNS == "", Class == request ->
invalid;
true ->
#iq{id = ID, #iq{id = ID,
type = Type1, type = Type1,
xmlns = XMLNS, xmlns = XMLNS,
lang = Lang, lang = Lang,
sub_el = {xmlelement, Name2, Attrs2, Els2}}; sub_el = SubEl}
true ->
invalid
end; end;
_ -> Class == reply, Filter /= any ->
invalid reply
end;
true ->
Type1
end; end;
iq_query_info(_) -> iq_info_internal(_, _) ->
not_iq. not_iq.
is_iq_request_type(set) -> true; is_iq_request_type(set) -> true;

View File

@ -33,6 +33,7 @@
-define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event"). -define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
-define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner"). -define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
-define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info"). -define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info").
-define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors").
-define(NS_COMMANDS, "http://jabber.org/protocol/commands"). -define(NS_COMMANDS, "http://jabber.org/protocol/commands").
-define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams"). -define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams").
-define(NS_ADMIN, "http://jabber.org/protocol/admin"). -define(NS_ADMIN, "http://jabber.org/protocol/admin").
@ -55,6 +56,8 @@
-define(NS_COMPRESS, "http://jabber.org/protocol/compress"). -define(NS_COMPRESS, "http://jabber.org/protocol/compress").
-define(NS_CAPS, "http://jabber.org/protocol/caps").
% TODO: remove "code" attribute (currently it used for backward-compatibility) % TODO: remove "code" attribute (currently it used for backward-compatibility)
-define(STANZA_ERROR(Code, Type, Condition), -define(STANZA_ERROR(Code, Type, Condition),
{xmlelement, "error", {xmlelement, "error",

259
src/mod_caps.erl Normal file
View File

@ -0,0 +1,259 @@
%%%----------------------------------------------------------------------
%%% File : mod_caps.erl
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
%%% Purpose : Request and cache Entity Capabilities (XEP-0115)
%%% Created : 7 Oct 2006 by Magnus Henoch <henoch@dtek.chalmers.se>
%%%----------------------------------------------------------------------
-module(mod_caps).
-author('henoch@dtek.chalmers.se').
-behaviour(gen_server).
-behaviour(gen_mod).
-export([read_caps/1,
note_caps/3,
get_features/2,
handle_disco_response/3]).
%% gen_mod callbacks
-export([start/2, start_link/2,
stop/1]).
%% gen_server callbacks
-export([init/1,
handle_info/2,
handle_call/3,
handle_cast/2,
terminate/2,
code_change/3
]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_caps).
-define(DICT, dict).
-record(caps, {node, version, exts}).
-record(caps_features, {node_pair, features}).
-record(state, {host,
disco_requests = ?DICT:new(),
feature_queries = []}).
%% read_caps takes a list of XML elements (the child elements of a
%% <presence/> stanza) and returns an opaque value representing the
%% Entity Capabilities contained therein, or the atom nothing if no
%% capabilities are advertised.
read_caps([{xmlelement, "c", Attrs, _Els} | Tail]) ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_CAPS ->
Node = xml:get_attr_s("node", Attrs),
Version = xml:get_attr_s("ver", Attrs),
Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "),
#caps{node = Node, version = Version, exts = Exts};
_ ->
read_caps(Tail)
end;
read_caps([_ | Tail]) ->
read_caps(Tail);
read_caps([]) ->
nothing.
%% 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;
_ ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:cast(Proc, {note_caps, From, Caps})
end.
%% get_features returns a list of features implied by the given caps
%% record (as extracted by read_caps). It may block, and may signal a
%% timeout error.
get_features(Host, Caps) ->
case Caps of
nothing -> [];
#caps{} ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, {get_features, Caps})
end.
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
transient,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:stop_child(ejabberd_sup, Proc).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Host, _Opts]) ->
mnesia:create_table(caps_features,
[{ram_copies, [node()]},
{attributes, record_info(fields, caps_features)}]),
mnesia:add_table_copy(caps_features, node(), ram_copies),
{ok, #state{host = Host}}.
maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
SubNodes = [Version | Exts],
F = fun() ->
%% Make sure that we have all nodes we need to know.
%% If a single one is missing, we wait for more disco
%% responses.
lists:foldl(fun(SubNode, Acc) ->
case Acc of
fail -> fail;
_ ->
case mnesia:read({caps_features, {Node, SubNode}}) of
[] -> fail;
[#caps_features{features = Features}] -> Features ++ Acc
end
end
end, [], SubNodes)
end,
case mnesia:transaction(F) of
{atomic, fail} ->
wait;
{atomic, Features} ->
{ok, Features}
end.
timestamp() ->
{MegaSecs, Secs, _MicroSecs} = now(),
MegaSecs * 1000000 + Secs.
handle_call({get_features, Caps}, From, State) ->
case maybe_get_features(Caps) of
{ok, Features} ->
{reply, Features, State};
wait ->
Timeout = timestamp() + 10,
FeatureQueries = State#state.feature_queries,
NewFeatureQueries = [{From, Caps, Timeout} | FeatureQueries],
NewState = State#state{feature_queries = NewFeatureQueries},
{noreply, NewState}
end;
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
handle_cast({note_caps, From,
#caps{node = Node, version = Version, exts = Exts}},
#state{host = Host, disco_requests = Requests} = State) ->
%% XXX: this leads to race conditions where ejabberd will send
%% lots of caps disco requests.
SubNodes = [Version | Exts],
%% Now, find which of these are not already in the database.
Fun = fun() ->
lists:foldl(fun(SubNode, Acc) ->
case mnesia:read({caps_features, {Node, SubNode}}) of
[] ->
[SubNode | Acc];
_ ->
Acc
end
end, [], SubNodes)
end,
case mnesia:transaction(Fun) of
{atomic, Missing} ->
%% For each unknown caps "subnode", we send a disco
%% request.
NewRequests =
lists:foldl(
fun(SubNode, Dict) ->
ID = randoms:get_string(),
Stanza =
{xmlelement, "iq",
[{"type", "get"},
{"id", ID}],
[{xmlelement, "query",
[{"xmlns", ?NS_DISCO_INFO},
{"node", Node ++ "#" ++ SubNode}],
[]}]},
ejabberd_local:register_iq_response_handler
(Host, ID, ?MODULE, handle_disco_response),
ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza),
?DICT:store(ID, {Node, SubNode}, Dict)
end, Requests, Missing),
{noreply, State#state{disco_requests = NewRequests}};
Error ->
?ERROR_MSG("Transaction failed: ~p", [Error]),
{noreply, State}
end;
handle_cast({disco_response, From, _To,
#iq{type = Type, id = ID,
sub_el = SubEls}},
#state{disco_requests = Requests} = State) ->
case {Type, SubEls} of
{result, [{xmlelement, "query", Attrs, Els}]} ->
case ?DICT:find(ID, Requests) of
{ok, {Node, SubNode}} ->
Features =
lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) ->
[xml:get_attr_s("var", FAttrs)];
(_) ->
[]
end, Els),
mnesia:transaction(
fun() ->
mnesia:write(#caps_features{node_pair = {Node, SubNode},
features = Features})
end),
gen_server:cast(self(), visit_feature_queries);
error ->
?ERROR_MSG("ID '~s' matches no query", [ID])
end;
{result, _} ->
?ERROR_MSG("Invalid IQ contents from ~s: ~p", [jlib:jid_to_string(From), SubEls]);
_ ->
%% Can't do anything about errors
ok
end,
NewRequests = ?DICT:erase(ID, Requests),
{noreply, State#state{disco_requests = NewRequests}};
handle_cast(visit_feature_queries, #state{feature_queries = FeatureQueries} = State) ->
Timestamp = timestamp(),
NewFeatureQueries =
lists:foldl(fun({From, Caps, Timeout}, Acc) ->
case maybe_get_features(Caps) of
wait when Timeout < Timestamp -> [{From, Caps, Timeout} | Acc];
wait -> Acc;
{ok, Features} ->
gen_server:reply(From, Features),
Acc
end
end, [], FeatureQueries),
{noreply, State#state{feature_queries = NewFeatureQueries}}.
handle_disco_response(From, To, IQ) ->
#jid{lserver = Host} = To,
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:cast(Proc, {disco_response, From, To, IQ}).
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

View File

@ -15,11 +15,18 @@ OUTDIR = ..
EFLAGS = -I .. -pz .. EFLAGS = -I .. -pz ..
# make debug=true to compile Erlang module with debug informations. # make debug=true to compile Erlang module with debug informations.
ifdef debug ifdef debug
EFLAGS+=+debug_info EFLAGS+=+debug_info
endif endif
OBJS = \ OBJS = \
$(OUTDIR)/mod_pubsub.beam $(OUTDIR)/gen_pubsub_node.beam \
$(OUTDIR)/gen_pubsub_nodetree.beam \
$(OUTDIR)/nodetree_default.beam \
$(OUTDIR)/nodetree_virtual.beam \
$(OUTDIR)/mod_pubsub.beam \
$(OUTDIR)/mod_pubsub_old.beam \
$(OUTDIR)/node_default.beam \
$(OUTDIR)/node_pep.beam
all: $(OBJS) all: $(OBJS)

View File

@ -5,12 +5,38 @@ OUTDIR = ..
EFLAGS = -I .. -pz .. EFLAGS = -I .. -pz ..
OBJS = \ OBJS = \
$(OUTDIR)\mod_pubsub.beam $(OUTDIR)/gen_pubsub_node.beam \
$(OUTDIR)/gen_pubsub_nodetree.beam \
$(OUTDIR)/nodetree_default.beam \
$(OUTDIR)/nodetree_virtual.beam \
$(OUTDIR)/mod_pubsub.beam \
$(OUTDIR)/mod_pubsub_old.beam \
$(OUTDIR)/node_default.beam \
$(OUTDIR)/node_pep.beam
ALL : $(OBJS) ALL : $(OBJS)
CLEAN : CLEAN :
-@erase $(OBJS) -@erase $(OBJS)
$(OUTDIR)\gen_pubsub_node.beam : gen_pubsub_node.erl
erlc -W $(EFLAGS) -o $(OUTDIR) gen_pubsub_node.erl
$(OUTDIR)\gen_pubsub_nodetree.beam : gen_pubsub_nodetree.erl
erlc -W $(EFLAGS) -o $(OUTDIR) gen_pubsub_nodetree.erl
$(OUTDIR)\mod_pubsub.beam : mod_pubsub.erl $(OUTDIR)\mod_pubsub.beam : mod_pubsub.erl
erlc -W $(EFLAGS) -o $(OUTDIR) mod_pubsub.erl erlc -W $(EFLAGS) -o $(OUTDIR) mod_pubsub.erl
$(OUTDIR)\nodetree_default.beam : nodetree_default.erl
erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_default.erl
$(OUTDIR)\nodetree_virtual.beam : nodetree_virtual.erl
erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_virtual.erl
$(OUTDIR)\node_default.beam : node_default.erl
erlc -W $(EFLAGS) -o $(OUTDIR) node_default.erl
$(OUTDIR)\node_pep.beam : node_pep.erl
erlc -W $(EFLAGS) -o $(OUTDIR) node_pep.erl

View File

@ -0,0 +1,55 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2006 Process-one
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @private
%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node
%%% plugin behaviour. This behaviour is used to check that a PubSub plugin
%%% respects the current ejabberd PubSub plugin API.</p>
-module(gen_pubsub_node).
-export([behaviour_info/1]).
%% @spec (Query::atom()) -> Callbacks | atom()
%% Callbacks = [{Function,Arity}]
%% Function = atom()
%% Arity = integer()
%% @doc Behaviour definition
behaviour_info(callbacks) ->
[{init, 3},
{terminate, 2},
{options, 0},
{features, 0},
{create_node_permission, 6},
{create_node, 3},
{delete_node, 2},
{purge_node, 3},
{subscribe_node, 8},
{unsubscribe_node, 5},
{publish_item, 7},
{delete_item, 4},
{remove_extra_items, 4},
{get_node_affiliations, 2},
{get_entity_affiliations, 2},
{get_affiliation, 3},
{set_affiliation, 4},
{get_node_subscriptions, 2},
{get_entity_subscriptions, 2},
{get_subscription, 3},
{set_subscription, 4},
{get_states, 2},
{get_state, 3},
{set_state, 1},
{get_items, 2},
{get_item, 3},
{set_item, 1}
];
behaviour_info(_Other) ->
undefined.

View File

@ -0,0 +1,40 @@
%%% ====================================================================
%%% This software is copyright 2006, Process-one.
%%%
%%% $Id: gen_pubsub_nodetree.erl 100 2007-11-15 13:04:44Z mremond $
%%%
%%% @copyright 2006 Process-one
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @private
%%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node
%%% tree plugin behaviour. This behaviour is used to check that a PubSub
%%% node tree plugin respects the current ejabberd PubSub plugin API.</p>
-module(gen_pubsub_nodetree).
-export([behaviour_info/1]).
%% @spec (Query::atom()) -> Callbacks | atom()
%% Callbacks = [{Function,Arity}]
%% Function = atom()
%% Arity = integer()
%% @doc Behaviour definition
behaviour_info(callbacks) ->
[{init, 3},
{terminate, 2},
{options, 0},
{set_node, 1},
{get_node, 2},
{get_nodes, 1},
{get_subnodes, 2},
{get_subnodes_tree, 2},
{create_node, 5},
{delete_node, 2}
];
behaviour_info(_Other) ->
undefined.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,163 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(__TO_BE_DEFINED__).
-author(__TO_BE_DEFINED__).
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% Note on function definition
%% included is all defined plugin function
%% it's possible not to define some function at all
%% in that case, warning will be generated at compilation
%% and function call will fail,
%% then mod_pubsub will call function from node_default
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/3,
delete_node/2,
purge_node/3,
subscribe_node/8,
unsubscribe_node/5,
publish_item/7,
delete_item/4,
remove_extra_items/4,
get_entity_affiliations/2,
get_node_affiliations/2,
get_affiliation/3,
set_affiliation/4,
get_entity_subscriptions/2,
get_node_subscriptions/2,
get_subscription/3,
set_subscription/4,
get_states/2,
get_state/3,
set_state/1,
get_items/2,
get_item/3,
set_item/1
]).
init(Host, ServerHost, Opts) ->
node_default:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_default:terminate(Host, ServerHost).
options() ->
[{node_type, __TO_BE_DEFINED__},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{persist_items, true},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, open},
{access_roster_groups, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
{presence_based_delivery, false}].
features() ->
["create-nodes",
"delete-nodes",
"instant-nodes",
"item-ids",
"outcast-affiliation",
"persistent-items",
"publish",
"purge-nodes",
"retract-items",
"retrieve-affiliations",
"retrieve-items",
"retrieve-subscriptions",
"subscribe",
"subscription-notifications"
].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Host, Node, Owner) ->
node_default:create_node(Host, Node, Owner).
delete_node(Host, Removed) ->
node_default:delete_node(Host, Removed).
subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Host, Node, MaxItems, ItemIds) ->
node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
delete_item(Host, Node, JID, ItemId) ->
node_default:delete_item(Host, Node, JID, ItemId).
purge_node(Host, Node, Owner) ->
node_default:purge_node(Host, Node, Owner).
get_entity_affiliations(Host, Owner) ->
node_default:get_entity_affiliations(Host, Owner).
get_node_affiliations(Host, Node) ->
node_default:get_node_affiliations(Host, Node).
get_affiliation(Host, Node, Owner) ->
node_default:get_affiliation(Host, Node, Owner).
set_affiliation(Host, Node, Owner, Affiliation) ->
node_default:set_affiliation(Host, Node, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_default:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Host, Node) ->
node_default:get_node_subscriptions(Host, Node).
get_subscription(Host, Node, Owner) ->
node_default:get_subscription(Host, Node, Owner).
set_subscription(Host, Node, Owner, Subscription) ->
node_default:set_subscription(Host, Node, Owner, Subscription).
get_states(Host, Node) ->
node_default:get_states(Host, Node).
get_state(Host, Node, JID) ->
node_default:get_state(Host, Node, JID).
set_state(State) ->
node_default:set_state(State).
get_items(Host, Node) ->
node_default:get_items(Host, Node).
get_item(Host, Node, ItemId) ->
node_default:get_items(Host, Node, ItemId).
set_item(Item) ->
node_default:set_item(Item).

View File

@ -0,0 +1,163 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(node_buddy).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% Note on function definition
%% included is all defined plugin function
%% it's possible not to define some function at all
%% in that case, warning will be generated at compilation
%% and function call will fail,
%% then mod_pubsub will call function from node_default
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/3,
delete_node/2,
purge_node/3,
subscribe_node/8,
unsubscribe_node/5,
publish_item/7,
delete_item/4,
remove_extra_items/4,
get_entity_affiliations/2,
get_node_affiliations/2,
get_affiliation/3,
set_affiliation/4,
get_entity_subscriptions/2,
get_node_subscriptions/2,
get_subscription/3,
set_subscription/4,
get_states/2,
get_state/3,
set_state/1,
get_items/2,
get_item/3,
set_item/1
]).
init(Host, ServerHost, Opts) ->
node_default:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_default:terminate(Host, ServerHost).
options() ->
[{node_type, buddy},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{persist_items, true},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, presence},
{access_roster_groups, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
{presence_based_delivery, false}].
features() ->
["create-nodes",
"delete-nodes",
"instant-nodes",
"item-ids",
"outcast-affiliation",
"persistent-items",
"publish",
"purge-nodes",
"retract-items",
"retrieve-affiliations",
"retrieve-items",
"retrieve-subscriptions",
"subscribe",
"subscription-notifications"
].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Host, Node, Owner) ->
node_default:create_node(Host, Node, Owner).
delete_node(Host, Removed) ->
node_default:delete_node(Host, Removed).
subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Host, Node, MaxItems, ItemIds) ->
node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
delete_item(Host, Node, JID, ItemId) ->
node_default:delete_item(Host, Node, JID, ItemId).
purge_node(Host, Node, Owner) ->
node_default:purge_node(Host, Node, Owner).
get_entity_affiliations(Host, Owner) ->
node_default:get_entity_affiliations(Host, Owner).
get_node_affiliations(Host, Node) ->
node_default:get_node_affiliations(Host, Node).
get_affiliation(Host, Node, Owner) ->
node_default:get_affiliation(Host, Node, Owner).
set_affiliation(Host, Node, Owner, Affiliation) ->
node_default:set_affiliation(Host, Node, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_default:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Host, Node) ->
node_default:get_node_subscriptions(Host, Node).
get_subscription(Host, Node, Owner) ->
node_default:get_subscription(Host, Node, Owner).
set_subscription(Host, Node, Owner, Subscription) ->
node_default:set_subscription(Host, Node, Owner, Subscription).
get_states(Host, Node) ->
node_default:get_states(Host, Node).
get_state(Host, Node, JID) ->
node_default:get_state(Host, Node, JID).
set_state(State) ->
node_default:set_state(State).
get_items(Host, Node) ->
node_default:get_items(Host, Node).
get_item(Host, Node, ItemId) ->
node_default:get_items(Host, Node, ItemId).
set_item(Item) ->
node_default:set_item(Item).

View File

@ -0,0 +1,163 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(node_club).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% Note on function definition
%% included is all defined plugin function
%% it's possible not to define some function at all
%% in that case, warning will be generated at compilation
%% and function call will fail,
%% then mod_pubsub will call function from node_default
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/3,
delete_node/2,
purge_node/3,
subscribe_node/8,
unsubscribe_node/5,
publish_item/7,
delete_item/4,
remove_extra_items/4,
get_entity_affiliations/2,
get_node_affiliations/2,
get_affiliation/3,
set_affiliation/4,
get_entity_subscriptions/2,
get_node_subscriptions/2,
get_subscription/3,
set_subscription/4,
get_states/2,
get_state/3,
set_state/1,
get_items/2,
get_item/3,
set_item/1
]).
init(Host, ServerHost, Opts) ->
node_default:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_default:terminate(Host, ServerHost).
options() ->
[{node_type, club},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{persist_items, true},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, authorize},
{access_roster_groups, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
{presence_based_delivery, false}].
features() ->
["create-nodes",
"delete-nodes",
"instant-nodes",
"item-ids",
"outcast-affiliation",
"persistent-items",
"publish",
"purge-nodes",
"retract-items",
"retrieve-affiliations",
"retrieve-items",
"retrieve-subscriptions",
"subscribe",
"subscription-notifications"
].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Host, Node, Owner) ->
node_default:create_node(Host, Node, Owner).
delete_node(Host, Removed) ->
node_default:delete_node(Host, Removed).
subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Host, Node, MaxItems, ItemIds) ->
node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
delete_item(Host, Node, JID, ItemId) ->
node_default:delete_item(Host, Node, JID, ItemId).
purge_node(Host, Node, Owner) ->
node_default:purge_node(Host, Node, Owner).
get_entity_affiliations(Host, Owner) ->
node_default:get_entity_affiliations(Host, Owner).
get_node_affiliations(Host, Node) ->
node_default:get_node_affiliations(Host, Node).
get_affiliation(Host, Node, Owner) ->
node_default:get_affiliation(Host, Node, Owner).
set_affiliation(Host, Node, Owner, Affiliation) ->
node_default:set_affiliation(Host, Node, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_default:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Host, Node) ->
node_default:get_node_subscriptions(Host, Node).
get_subscription(Host, Node, Owner) ->
node_default:get_subscription(Host, Node, Owner).
set_subscription(Host, Node, Owner, Subscription) ->
node_default:set_subscription(Host, Node, Owner, Subscription).
get_states(Host, Node) ->
node_default:get_states(Host, Node).
get_state(Host, Node, JID) ->
node_default:get_state(Host, Node, JID).
set_state(State) ->
node_default:set_state(State).
get_items(Host, Node) ->
node_default:get_items(Host, Node).
get_item(Host, Node, ItemId) ->
node_default:get_items(Host, Node, ItemId).
set_item(Item) ->
node_default:set_item(Item).

View File

@ -0,0 +1,712 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @todo The item table should be handled by the plugin, but plugin that do
%%% not want to manage it should be able to use the default behaviour.
%%% @todo Plugin modules should be able to register to receive presence update
%%% send to pubsub.
%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
%%% <p>It is used as a default for all unknown PubSub node type. It can serve
%%% as a developer basis and reference to build its own custom pubsub node
%%% types.</p>
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
%%% development is still a work in progress. However, the system is already
%%% useable and useful as is. Please, send us comments, feedback and
%%% improvements.</p>
-module(node_default).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/3,
delete_node/2,
purge_node/3,
subscribe_node/8,
unsubscribe_node/5,
publish_item/7,
delete_item/4,
remove_extra_items/4,
get_entity_affiliations/2,
get_node_affiliations/2,
get_affiliation/3,
set_affiliation/4,
get_entity_subscriptions/2,
get_node_subscriptions/2,
get_subscription/3,
set_subscription/4,
get_states/2,
get_state/3,
set_state/1,
get_items/2,
get_item/3,
set_item/1
]).
%% ================
%% API definition
%% ================
%% @spec (Host) -> any()
%% Host = mod_pubsub:host()
%% ServerHost = host()
%% Opts = list()
%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
%% implement this function. It can return anything.</p>
%% <p>This function is mainly used to trigger the setup task necessary for the
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
init(_Host, _ServerHost, _Opts) ->
mnesia:create_table(pubsub_state,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_state)}]),
StatesFields = record_info(fields, pubsub_state),
case mnesia:table_info(pubsub_state, attributes) of
StatesFields -> ok;
_ ->
mnesia:transform_table(pubsub_state, ignore, StatesFields)
end,
mnesia:create_table(pubsub_item,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, pubsub_item)}]),
ItemsFields = record_info(fields, pubsub_item),
case mnesia:table_info(pubsub_item, attributes) of
ItemsFields -> ok;
_ ->
mnesia:transform_table(pubsub_item, ignore, ItemsFields)
end,
ok.
%% @spec (Host) -> any()
%% Host = mod_pubsub:host()
%% @doc <p>Called during pubsub modules termination. Any pubsub plugin must
%% implement this function. It can return anything.</p>
terminate(_Host, _ServerHost) ->
ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodeOption()
%% @doc Returns the default pubsub node options.
%% <p>Example of function return value:</p>
%% ```
%% [{deliver_payloads, true},
%% {notify_config, false},
%% {notify_delete, false},
%% {notify_retract, true},
%% {persist_items, true},
%% {max_items, 10},
%% {subscribe, true},
%% {access_model, open},
%% {publish_model, publishers},
%% {max_payload_size, 100000},
%% {send_last_published_item, never},
%% {presence_based_delivery, false}]'''
options() ->
[{node_type, default},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{persist_items, true},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, open},
{access_roster_groups, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
{presence_based_delivery, false}].
%% @spec () -> []
%% @doc Returns the node features
features() ->
["create-nodes",
"auto-create",
"delete-nodes",
"instant-nodes",
"item-ids",
"manage-subscriptions",
"outcast-affiliation",
"persistent-items",
"publish",
"purge-nodes",
"retract-items",
"retrieve-affiliations",
"retrieve-items",
"retrieve-subscriptions",
"subscribe",
"subscription-notifications"
].
%% @spec (Host, Node, Owner, Access) -> bool()
%% Host = mod_pubsub:host()
%% ServerHost = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Owner = mod_pubsub:jid()
%% Access = all | atom()
%% @doc Checks if the current user has the permission to create the requested node
%% <p>In {@link node_default}, the permission is decided by the place in the
%% hierarchy where the user is creating the node. The access parameter is also
%% checked in the default module. This parameter depends on the value of the
%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
%% <p>This function also check that node can be created a a children of its
%% parent node</p>
%% <p>PubSub plugins can redefine the PubSub node creation rights as they
%% which. They can simply delegate this check to the {@link node_default}
%% module by implementing this function like this:
%% ```check_create_user_permission(Host, Node, Owner, Access) ->
%% node_default:check_create_user_permission(Host, Node, Owner, Access).'''</p>
create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case acl:match_rule(ServerHost, Access, LOwner) of
allow ->
if Server == Host -> %% Server == ServerHost ??
true;
true ->
case Node of
["home", Server, User | _] -> true;
_ -> false
end
end;
_ ->
case Owner of
?PUBSUB_JID -> true;
_ -> false
end
end,
ChildOK = true, %% TODO test with ParentNode
{result, Allowed and ChildOK}.
%% @spec (Host, Node, Owner) ->
%% {result, Result} | exit
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Owner = mod_pubsub:jid()
%% @doc <p></p>
create_node(Host, Node, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
mnesia:write(#pubsub_state{stateid = {OwnerKey, {Host, Node}},
affiliation = owner, subscription = none}),
{result, {default, broadcast}}.
%% @spec (Host, Removed) -> ok
%% Host = mod_pubsub:host()
%% Removed = [mod_pubsub:pubsubNode()]
%% @doc <p>purge items of deleted nodes after effective deletion.</p>
delete_node(Host, Removed) ->
lists:foreach(
fun(Node) ->
lists:foreach(
fun(#pubsub_state{stateid = StateId, items = Items}) ->
lists:foreach(
fun(ItemId) ->
mnesia:delete(
{pubsub_item, {ItemId, {Host, Node}}})
end, Items),
mnesia:delete({pubsub_state, StateId})
end,
mnesia:match_object(
#pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}))
end, Removed),
{result, {default, broadcast, Removed}}.
%% @spec (Host, Node, Sender, Subscriber, AccessModel, SendLast) ->
%% {error, Reason} | {result, Result}
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
%% <p>The mechanism works as follow:
%% <ul>
%% <li>The main PubSub module prepares the subscription and passes the
%% result of the preparation as a record.</li>
%% <li>This function gets the prepared record and several other parameters and
%% can decide to:<ul>
%% <li>reject the subscription;</li>
%% <li>allow it as is, letting the main module perform the database
%% persistance;</li>
%% <li>allow it, modifying the record. The main module will store the
%% modified record;</li>
%% <li>allow it, but perform the needed persistance operations.</li></ul>
%% </li></ul></p>
%% <p>The selected behaviour depends on the return parameter:
%% <ul>
%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
%% subscription will actually be performed.</li>
%% <li><tt>true</tt>: Subscribe operation is allowed, based on the
%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
%% parameter contains an error, no subscription will be performed.</li>
%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
%% the {@link mod_pubsub:pubsubState()} record returned replaces the value
%% passed in parameter <tt>SubscribeResult</tt>.</li>
%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
%% {@link mod_pubsub:pubsubState()} will be considered as already stored and
%% no further persistance operation will be performed. This case is used,
%% when the plugin module is doing the persistance by itself or when it want
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(Host, Node, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup) ->
SenderKey = jlib:jid_tolower(Sender),
Authorized = (jlib:jid_remove_resource(SenderKey) == jlib:jid_remove_resource(Subscriber)),
% TODO add some acl check for Authorized ?
State = case get_state(Host, Node, Subscriber) of
{error, ?ERR_ITEM_NOT_FOUND} ->
#pubsub_state{stateid = {Subscriber, {Host, Node}}}; % TODO: bug on Key ?
{result, S} -> S
end,
#pubsub_state{affiliation = Affiliation,
subscription = Subscription} = State,
if
not Authorized ->
%% JIDs do not match
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")};
Subscription == pending ->
%% Requesting entity has pending subscription
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
Affiliation == outcast ->
%% Requesting entity is blocked
{error, ?ERR_FORBIDDEN};
(AccessModel == presence) and (not PresenceSubscription) ->
%% Entity is not authorized to create a subscription (presence subscription required)
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
(AccessModel == roster) and (not RosterGroup) ->
%% Entity is not authorized to create a subscription (not in roster group)
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
(AccessModel == whitelist) -> % TODO: to be done
%% Node has whitelist access model
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
(AccessModel == authorize) -> % TODO: to be done
%% Node has authorize access model
{error, ?ERR_FORBIDDEN};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
%%ForbiddenAnonymous ->
%% % Requesting entity is anonymous
%% {error, ?ERR_FORBIDDEN};
true ->
NewSubscription =
if
AccessModel == authorize ->
pending;
%%TODO Affiliation == none -> ?
%%NeedConfiguration ->
%% unconfigured
true ->
subscribed
end,
set_state(State#pubsub_state{subscription = NewSubscription}),
case NewSubscription of
subscribed ->
case SendLast of
never -> {result, {default, NewSubscription}};
_ -> {result, {default, NewSubscription, send_last}}
end;
_ ->
{result, {default, NewSubscription}}
end
end.
%% @spec (Host, Node, Sender, Subscriber, SubID) ->
%% {error, Reason} | {result, []}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Sender = mod_pubsub:jid()
%% Subscriber = mod_pubsub:jid()
%% SubID = string()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(Host, Node, Sender, Subscriber, _SubId) ->
SenderKey = jlib:jid_tolower(Sender),
Match = jlib:jid_remove_resource(SenderKey) == jlib:jid_remove_resource(Subscriber),
Authorized = case Match of
true ->
true;
false ->
case get_state(Host, Node, SenderKey) of % TODO: bug on Key ?
{result, #pubsub_state{affiliation=owner}} -> true;
_ -> false
end
end,
case get_state(Host, Node, Subscriber) of
{error, ?ERR_ITEM_NOT_FOUND} ->
%% Requesting entity is not a subscriber
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
{result, State} ->
if
%% Entity did not specify SubID
%%SubID == "", ?? ->
%% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%% Invalid subscription identifier
%%InvalidSubID ->
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
%% Requesting entity is not a subscriber
State#pubsub_state.subscription == none ->
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
%% Requesting entity is prohibited from unsubscribing entity
not Authorized ->
{error, ?ERR_FORBIDDEN};
true ->
set_state(State#pubsub_state{subscription = none}),
{result, default}
end
end.
%% @spec (Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
%% {true, PubsubItem} | {result, Reply}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Publisher = mod_pubsub:jid()
%% PublishModel = atom()
%% MaxItems = integer()
%% ItemId = string()
%% Payload = term()
%% @doc <p>Publishes the item passed as parameter.</p>
%% <p>The mechanism works as follow:
%% <ul>
%% <li>The main PubSub module prepares the item to publish and passes the
%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
%% <li>reject the publication;</li>
%% <li>allow the publication as is, letting the main module perform the database persistance;</li>
%% <li>allow the publication, modifying the record. The main module will store the modified record;</li>
%% <li>allow it, but perform the needed persistance operations.</li></ul>
%% </li></ul></p>
%% <p>The selected behaviour depends on the return parameter:
%% <ul>
%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No
%% publication is actually performed.</li>
%% <li><tt>true</tt>: Publication operation is allowed, based on the
%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
%% parameter contains an error, no subscription will actually be
%% performed.</li>
%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
%% in parameter <tt>Item</tt>. The persistance will be performed by the main
%% module.</li>
%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the
%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and
%% no further persistance operation will be performed. This case is used,
%% when the plugin module is doing the persistance by itself or when it want
%% to completly disable persistance.</li></ul>
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
publish_item(Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
PublisherKey = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
State = case get_state(Host, Node, PublisherKey) of
{error, ?ERR_ITEM_NOT_FOUND} -> #pubsub_state{stateid={PublisherKey, {Host, Node}}};
{result, S} -> S
end,
#pubsub_state{affiliation = Affiliation,
subscription = Subscription} = State,
if
not ((PublishModel == open)
or ((PublishModel == publishers)
and ((Affiliation == owner) or (Affiliation == publisher)))
or ((PublishModel == subscribers)
and (Subscription == subscribed))) ->
%% Entity does not have sufficient privileges to publish to node
{error, ?ERR_FORBIDDEN};
true ->
PubId = {PublisherKey, now()},
Item = case get_item(Host, Node, ItemId) of
{error, ?ERR_ITEM_NOT_FOUND} ->
#pubsub_item{itemid = {ItemId, {Host, Node}},
creation = PubId,
modification = PubId,
payload = Payload};
{result, OldItem} ->
OldItem#pubsub_item{modification = PubId,
payload = Payload}
end,
Items = [ItemId | State#pubsub_state.items],
{result, {NI, OI}} = remove_extra_items(
Host, Node, MaxItems, Items),
set_item(Item),
set_state(State#pubsub_state{items = NI}),
{result, {default, broadcast, OI}}
end.
%% @spec (Host, Node, MaxItems, ItemIds) -> {NewItemIds,OldItemIds}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% MaxItems = integer() | unlimited
%% ItemIds = [ItemId::string()]
%% NewItemIds = [ItemId::string()]
%% @doc <p>This function is used to remove extra items, most notably when the
%% maximum number of items has been reached.</p>
%% <p>This function is used internally by the core PubSub module, as no
%% permission check is performed.</p>
%% <p>In the default plugin module, the oldest items are removed, but other
%% rules can be used.</p>
%% <p>If another PubSub plugin wants to delegate the item removal (and if the
%% plugin is using the default pubsub storage), it can implements this function like this:
%% ```remove_extra_items(Host, Node, MaxItems, ItemIds) ->
%% node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).'''</p>
remove_extra_items(_Host, _Node, unlimited, ItemIds) ->
{result, {ItemIds, []}};
remove_extra_items(Host, Node, MaxItems, ItemIds) ->
NewItems = lists:sublist(ItemIds, MaxItems),
OldItems = lists:nthtail(length(NewItems), ItemIds),
%% Remove extra items:
lists:foreach(fun(ItemId) ->
mnesia:delete({pubsub_item, {ItemId, {Host, Node}}})
end, OldItems),
%% Return the new items list:
{result, {NewItems, OldItems}}.
%% @spec (Host, Node, JID, ItemId) ->
%% {error, Reason::stanzaError()} |
%% {result, []}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% JID = mod_pubsub:jid()
%% ItemId = string()
%% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a node publisher e item publisher.</p>
delete_item(Host, Node, Publisher, ItemId) ->
PublisherKey = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
State = case get_state(Host, Node, PublisherKey) of
{error, ?ERR_ITEM_NOT_FOUND} ->
#pubsub_state{stateid = {PublisherKey, {Host, Node}}};
{result, S} ->
S
end,
#pubsub_state{affiliation = Affiliation, items = Items} = State,
Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
orelse case get_item(Host, Node, ItemId) of
{result, #pubsub_item{creation = {PublisherKey, _}}} -> true;
_ -> false
end,
if
not Allowed ->
%% Requesting entity does not have sufficient privileges
{error, ?ERR_FORBIDDEN};
true ->
case get_item(Host, Node, ItemId) of
{result, _} ->
mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}),
NewItems = lists:delete(ItemId, Items),
set_state(State#pubsub_state{items = NewItems}),
{result, {default, broadcast}};
_ ->
%% Non-existent node or item
{error, ?ERR_ITEM_NOT_FOUND}
end
end.
%% @spec (TODO)
purge_node(Host, Node, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
case get_state(Host, Node, OwnerKey) of
{error, ?ERR_ITEM_NOT_FOUND} ->
%% This should not append (case node does not exists)
{error, ?ERR_ITEM_NOT_FOUND};
{result, #pubsub_state{items = Items, affiliation = owner}} ->
lists:foreach(fun(ItemId) ->
mnesia:delete({pubsub_item, {ItemId, {Host, Node}}})
end, Items),
{result, {default, broadcast}};
_ ->
%% Entity is not an owner
{error, ?ERR_FORBIDDEN}
end.
%% @spec (Host, JID) -> [{Node,Affiliation}]
%% Host = host()
%% JID = mod_pubsub:jid()
%% @doc <p>Return the current affiliations for the given user</p>
%% <p>The default module reads affiliations in the main Mnesia
%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
%% table, it should return an empty list, as the affiliation will be read by
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
get_entity_affiliations(Host, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
States = mnesia:match_object(
#pubsub_state{stateid = {OwnerKey, {Host, '_'}},
_ = '_'}),
Tr = fun(#pubsub_state{stateid = {_, {_, N}}, affiliation = A}) ->
{N, A}
end,
{result, lists:map(Tr, States)}.
get_node_affiliations(Host, Node) ->
States = mnesia:match_object(
#pubsub_state{stateid = {'_', {Host, Node}},
_ = '_'}),
Tr = fun(#pubsub_state{stateid = {J, {_, _}}, affiliation = A}) ->
{J, A}
end,
{result, lists:map(Tr, States)}.
get_affiliation(Host, Node, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
Affiliation = case get_state(Host, Node, OwnerKey) of
{result, #pubsub_state{affiliation = A}} -> A;
_ -> unknown
end,
{result, Affiliation}.
set_affiliation(Host, Node, Owner, Affiliation) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
Record = case get_state(Host, Node, OwnerKey) of
{error, ?ERR_ITEM_NOT_FOUND} ->
#pubsub_state{stateid = {OwnerKey, {Host, Node}},
affiliation = Affiliation};
{result, State} ->
State#pubsub_state{affiliation = Affiliation}
end,
set_state(Record),
ok.
%% @spec (Host) -> [{Node,Subscription}]
%% Host = host()
%% JID = mod_pubsub:jid()
%% @doc <p>Return the current subscriptions for the given user</p>
%% <p>The default module reads subscriptions in the main Mnesia
%% <tt>pubsub_state</tt> table. If a plugin stores its data in the same
%% table, it should return an empty list, as the affiliation will be read by
%% the default PubSub module. Otherwise, it should return its own affiliation,
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
get_entity_subscriptions(Host, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
States = mnesia:match_object(
#pubsub_state{stateid = {OwnerKey, {Host, '_'}},
_ = '_'}),
Tr = fun(#pubsub_state{stateid = {_, {_, N}}, subscription = S}) ->
{N, S}
end,
{result, lists:map(Tr, States)}.
get_node_subscriptions(Host, Node) ->
States = mnesia:match_object(
#pubsub_state{stateid = {'_', {Host, Node}},
_ = '_'}),
Tr = fun(#pubsub_state{stateid = {J, {_, _}}, subscription = S}) ->
{J, S}
end,
{result, lists:map(Tr, States)}.
get_subscription(Host, Node, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
Subscription = case get_state(Host, Node, OwnerKey) of
{result, #pubsub_state{subscription = S}} -> S;
_ -> unknown
end,
{result, Subscription}.
set_subscription(Host, Node, Owner, Subscription) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
Record = case get_state(Host, Node, OwnerKey) of
{error, ?ERR_ITEM_NOT_FOUND} ->
#pubsub_state{stateid = {OwnerKey, {Host, Node}},
subscription = Subscription};
{result, State} ->
State#pubsub_state{subscription = Subscription}
end,
set_state(Record),
ok.
%% @spec (Host, Node) -> [States] | []
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Item = mod_pubsub:pubsubItems()
%% @doc Returns the list of stored states for a given node.
%% <p>For the default PubSub module, states are stored in Mnesia database.</p>
%% <p>We can consider that the pubsub_state table have been created by the main
%% mod_pubsub module.</p>
%% <p>PubSub plugins can store the states where they wants (for example in a
%% relational database).</p>
%% <p>If a PubSub plugin wants to delegate the states storage to the default node,
%% they can implement this function like this:
%% ```get_states(Host, Node) ->
%% node_default:get_states(Host, Node).'''</p>
get_states(Host, Node) ->
States = mnesia:match_object(
#pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}),
{result, States}.
%% @spec (JID, Host, Node) -> [State] | []
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% JID = mod_pubsub:jid()
%% State = mod_pubsub:pubsubItems()
%% @doc <p>Returns a state (one state list), given its reference.</p>
get_state(Host, Node, JID) ->
case mnesia:read({pubsub_state, {JID, {Host, Node}}}) of
[State] when is_record(State, pubsub_state) ->
{result, State};
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end.
%% @spec (State) -> ok | {error, ?ERR_INTERNAL_SERVER_ERROR}
%% State = mod_pubsub:pubsubStates()
%% @doc <p>Write a state into database.</p>
set_state(State) when is_record(State, pubsub_state) ->
mnesia:write(State);
set_state(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}.
%% @spec (Host, Node) -> [Items] | []
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Items = mod_pubsub:pubsubItems()
%% @doc Returns the list of stored items for a given node.
%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
%% <p>We can consider that the pubsub_item table have been created by the main
%% mod_pubsub module.</p>
%% <p>PubSub plugins can store the items where they wants (for example in a
%% relational database), or they can even decide not to persist any items.</p>
%% <p>If a PubSub plugin wants to delegate the item storage to the default node,
%% they can implement this function like this:
%% ```get_items(Host, Node) ->
%% node_default:get_items(Host, Node).'''</p>
get_items(Host, Node) ->
Items = mnesia:match_object(
#pubsub_item{itemid = {'_', {Host, Node}}, _ = '_'}),
{result, Items}.
%% @spec (Host, Node, ItemId) -> [Item] | []
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% ItemId = string()
%% Item = mod_pubsub:pubsubItems()
%% @doc <p>Returns an item (one item list), given its reference.</p>
get_item(Host, Node, ItemId) ->
case mnesia:read({pubsub_item, {ItemId, {Host, Node}}}) of
[Item] when is_record(Item, pubsub_item) ->
{result, Item};
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end.
%% @spec (Item) -> ok | {error, ?ERR_INTERNAL_SERVER_ERROR}
%% Item = mod_pubsub:pubsubItems()
%% @doc <p>Write a state into database.</p>
set_item(Item) when is_record(Item, pubsub_item) ->
mnesia:write(Item);
set_item(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}.

View File

@ -0,0 +1,166 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(node_dispatch).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%%% @doc <p>The <strong>{@module}</strong> module is a PubSub plugin whose
%%% goal is to republished each published item to all its children.</p>
%%% <p>Users cannot subscribe to this node, but are supposed to subscribe to
%%% its children.</p>
%%% This module can not work with virtual nodetree
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/3,
delete_node/2,
purge_node/3,
subscribe_node/8,
unsubscribe_node/5,
publish_item/7,
delete_item/4,
remove_extra_items/4,
get_entity_affiliations/2,
get_node_affiliations/2,
get_affiliation/3,
set_affiliation/4,
get_entity_subscriptions/2,
get_node_subscriptions/2,
get_subscription/3,
set_subscription/4,
get_states/2,
get_state/3,
set_state/1,
get_items/2,
get_item/3,
set_item/1
]).
init(Host, ServerHost, Opts) ->
node_default:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_default:terminate(Host, ServerHost).
options() ->
[{node_type, dispatch},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{persist_items, true},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, open},
{access_roster_groups, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
{presence_based_delivery, false}].
features() ->
["create-nodes",
"delete-nodes",
"instant-nodes",
"item-ids",
"outcast-affiliation",
"persistent-items",
"publish",
%%"purge-nodes",
%%"retract-items",
%%"retrieve-affiliations",
"retrieve-items",
%%"retrieve-subscriptions",
%%"subscribe",
%%"subscription-notifications",
].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Host, Node, Owner) ->
node_default:create_node(Host, Node, Owner).
delete_node(Host, Removed) ->
node_default:delete_node(Host, Removed).
subscribe_node(_Host, _Node, _Sender, _Subscriber, _AccessModel,
_SendLast, _PresenceSubscription, _RosterGroup) ->
{error, ?ERR_FORBIDDEN}.
unsubscribe_node(_Host, _Node, _Sender, _Subscriber, _SubID) ->
{error, ?ERR_FORBIDDEN}.
publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
lists:foreach(fun(SubNode) ->
node_default:publish_item(
Host, SubNode, Publisher, Model,
MaxItems, ItemId, Payload)
end, nodetree_default:get_subnodes(Host, Node)).
remove_extra_items(_Host, _Node, _MaxItems, ItemIds) ->
{result, {ItemsIds, []}}.
delete_item(_Host, _Node, _JID, _ItemId) ->
{error, ?ERR_ITEM_NOT_FOUND}.
purge_node(_Host, _Node, _Owner) ->
{error, ?ERR_FORBIDDEN}.
get_entity_affiliations(_Host, _Owner) ->
{result, []}.
get_node_affiliations(_Host, _Node) ->
{result, []}.
get_affiliation(_Host, _Node, _Owner) ->
{result, []}.
set_affiliation(Host, Node, Owner, Affiliation) ->
node_default:set_affiliation(Host, Node, Owner, Affiliation).
get_entity_subscriptions(_Host, _Owner) ->
{result, []}.
get_node_subscriptions(_Host, _Node) ->
{result, []}.
get_subscription(_Host, _Node, _Owner) ->
{result, []}.
set_subscription(Host, Node, Owner, Subscription) ->
node_default:set_subscription(Host, Node, Owner, Subscription).
get_states(Host, Node) ->
node_default:get_states(Host, Node).
get_state(Host, Node, JID) ->
node_default:get_state(Host, Node, JID).
set_state(State) ->
node_default:set_state(State).
get_items(Host, Node) ->
node_default:get_items(Host, Node).
get_item(Host, Node, ItemId) ->
node_default:get_items(Host, Node, ItemId).
set_item(Item) ->
node_default:set_item(Item).

173
src/mod_pubsub/node_pep.erl Normal file
View File

@ -0,0 +1,173 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-module(node_pep).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/3,
delete_node/2,
purge_node/3,
subscribe_node/8,
unsubscribe_node/5,
publish_item/7,
delete_item/4,
remove_extra_items/4,
get_entity_affiliations/2,
get_node_affiliations/2,
get_affiliation/3,
set_affiliation/4,
get_entity_subscriptions/2,
get_node_subscriptions/2,
get_subscription/3,
set_subscription/4,
get_states/2,
get_state/3,
set_state/1,
get_items/2,
get_item/3,
set_item/1
]).
init(Host, ServerHost, Opts) ->
node_default:init(Host, ServerHost, Opts),
ok.
terminate(Host, ServerHost) ->
node_default:terminate(Host, ServerHost),
ok.
options() ->
[{node_type, pep},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, false},
{persist_items, false},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, presence},
{access_roster_groups, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
{presence_based_delivery, true}].
features() ->
["create-nodes", %*
"auto-create", %*
"delete-nodes", %*
"item-ids",
"outcast-affiliation",
"persistent-items",
"publish", %*
"purge-nodes",
"retract-items",
"retrieve-affiliations",
"retrieve-items", %*
"retrieve-subscriptions",
"subscribe", %*
"auto-subscribe", %*
"filtered-notifications" %*
].
create_node_permission(_Host, _ServerHost, _Node, _ParentNode, _Owner, _Access) ->
%% TODO may we check bare JID match ?
{result, true}.
create_node(Host, Node, Owner) ->
case node_default:create_node(Host, Node, Owner) of
{result, _} -> {result, []};
Error -> Error
end.
delete_node(Host, Removed) ->
case node_default:delete_node(Host, Removed) of
{result, {_, _, Removed}} -> {result, {[], Removed}};
Error -> Error
end.
subscribe_node(Host, Node, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup) ->
node_default:subscribe_node(
Host, Node, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup).
unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
case node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Host, Node, MaxItems, ItemIds) ->
node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
delete_item(Host, Node, JID, ItemId) ->
node_default:delete_item(Host, Node, JID, ItemId).
purge_node(Host, Node, Owner) ->
node_default:purge_node(Host, Node, Owner).
get_entity_affiliations(Host, Owner) ->
node_default:get_entity_affiliations(Host, Owner).
get_node_affiliations(Host, Node) ->
node_default:get_node_affiliations(Host, Node).
get_affiliation(Host, Node, Owner) ->
node_default:get_affiliation(Host, Node, Owner).
set_affiliation(Host, Node, Owner, Affiliation) ->
node_default:set_affiliation(Host, Node, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_default:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Host, Node) ->
node_default:get_node_subscriptions(Host, Node).
get_subscription(Host, Node, Owner) ->
node_default:get_subscription(Host, Node, Owner).
set_subscription(Host, Node, Owner, Subscription) ->
node_default:set_subscription(Host, Node, Owner, Subscription).
get_states(Host, Node) ->
node_default:get_states(Host, Node).
get_state(Host, Node, JID) ->
node_default:get_state(Host, Node, JID).
set_state(State) ->
node_default:set_state(State).
get_items(Host, Node) ->
node_default:get_items(Host, Node).
get_item(Host, Node, ItemId) ->
node_default:get_items(Host, Node, ItemId).
set_item(Item) ->
node_default:set_item(Item).

View File

@ -0,0 +1,166 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(node_private).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% Note on function definition
%% included is all defined plugin function
%% it's possible not to define some function at all
%% in that case, warning will be generated at compilation
%% and function call will fail,
%% then mod_pubsub will call function from node_default
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/3,
delete_node/2,
purge_node/3,
subscribe_node/8,
unsubscribe_node/5,
publish_item/7,
delete_item/4,
remove_extra_items/4,
get_entity_affiliations/2,
get_node_affiliations/2,
get_affiliation/3,
set_affiliation/4,
get_entity_subscriptions/2,
get_node_subscriptions/2,
get_subscription/3,
set_subscription/4,
get_states/2,
get_state/3,
set_state/1,
get_items/2,
get_item/3,
set_item/1
]).
init(Host, ServerHost, Opts) ->
node_default:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_default:terminate(Host, ServerHost).
options() ->
[{node_type, private},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{persist_items, true},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, whitelist},
{access_roster_groups, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, false},
{presence_based_delivery, false}].
features() ->
["create-nodes",
"delete-nodes",
"instant-nodes",
"item-ids",
"outcast-affiliation",
"persistent-items",
"publish",
"purge-nodes",
"retract-items",
"retrieve-affiliations",
"retrieve-items",
"retrieve-subscriptions",
"subscribe",
"subscription-notifications"
].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_default:create_node_permission(Host, ServerHost, Node, ParentNode,
Owner, Access).
create_node(Host, Node, Owner) ->
node_default:create_node(Host, Node, Owner).
delete_node(Host, Removed) ->
node_default:delete_node(Host, Removed).
subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup) ->
node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup).
unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Host, Node, MaxItems, ItemIds) ->
node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
delete_item(Host, Node, JID, ItemId) ->
node_default:delete_item(Host, Node, JID, ItemId).
purge_node(Host, Node, Owner) ->
node_default:purge_node(Host, Node, Owner).
get_entity_affiliations(Host, Owner) ->
node_default:get_entity_affiliations(Host, Owner).
get_node_affiliations(Host, Node) ->
node_default:get_node_affiliations(Host, Node).
get_affiliation(Host, Node, Owner) ->
node_default:get_affiliation(Host, Node, Owner).
set_affiliation(Host, Node, Owner, Affiliation) ->
node_default:set_affiliation(Host, Node, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_default:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Host, Node) ->
node_default:get_node_subscriptions(Host, Node).
get_subscription(Host, Node, Owner) ->
node_default:get_subscription(Host, Node, Owner).
set_subscription(Host, Node, Owner, Subscription) ->
node_default:set_subscription(Host, Node, Owner, Subscription).
get_states(Host, Node) ->
node_default:get_states(Host, Node).
get_state(Host, Node, JID) ->
node_default:get_state(Host, Node, JID).
set_state(State) ->
node_default:set_state(State).
get_items(Host, Node) ->
node_default:get_items(Host, Node).
get_item(Host, Node, ItemId) ->
node_default:get_items(Host, Node, ItemId).
set_item(Item) ->
node_default:set_item(Item).

View File

@ -0,0 +1,165 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% $Id: node_public.erl 100 2007-11-15 13:04:44Z mremond $
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(node_public).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
%% Note on function definition
%% included is all defined plugin function
%% it's possible not to define some function at all
%% in that case, warning will be generated at compilation
%% and function call will fail,
%% then mod_pubsub will call function from node_default
%% (this makes code cleaner, but execution a little bit longer)
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/3,
delete_node/2,
purge_node/3,
subscribe_node/8,
unsubscribe_node/5,
publish_item/7,
delete_item/4,
remove_extra_items/4,
get_entity_affiliations/2,
get_node_affiliations/2,
get_affiliation/3,
set_affiliation/4,
get_entity_subscriptions/2,
get_node_subscriptions/2,
get_subscription/3,
set_subscription/4,
get_states/2,
get_state/3,
set_state/1,
get_items/2,
get_item/3,
set_item/1
]).
init(Host, ServerHost, Opts) ->
node_default:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_default:terminate(Host, ServerHost).
options() ->
[{node_type, public},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{persist_items, true},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, open},
{access_roster_groups, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, never},
{deliver_notifications, true},
{presence_based_delivery, false}].
features() ->
["create-nodes",
"delete-nodes",
"instant-nodes",
"item-ids",
"outcast-affiliation",
"persistent-items",
"publish",
"purge-nodes",
"retract-items",
"retrieve-affiliations",
"retrieve-items",
"retrieve-subscriptions",
"subscribe",
"subscription-notifications"
].
create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
node_default:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access).
create_node(Host, Node, Owner) ->
node_default:create_node(Host, Node, Owner).
delete_node(Host, Removed) ->
node_default:delete_node(Host, Removed).
subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
node_default:subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
unsubscribe_node(Host, Node, Sender, Subscriber, SubID) ->
node_default:unsubscribe_node(Host, Node, Sender, Subscriber, SubID).
publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload) ->
node_default:publish_item(Host, Node, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(Host, Node, MaxItems, ItemIds) ->
node_default:remove_extra_items(Host, Node, MaxItems, ItemIds).
delete_item(Host, Node, JID, ItemId) ->
node_default:delete_item(Host, Node, JID, ItemId).
purge_node(Host, Node, Owner) ->
node_default:purge_node(Host, Node, Owner).
get_entity_affiliations(Host, Owner) ->
node_default:get_entity_affiliations(Host, Owner).
get_node_affiliations(Host, Node) ->
node_default:get_node_affiliations(Host, Node).
get_affiliation(Host, Node, Owner) ->
node_default:get_affiliation(Host, Node, Owner).
set_affiliation(Host, Node, Owner, Affiliation) ->
node_default:set_affiliation(Host, Node, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_default:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(Host, Node) ->
node_default:get_node_subscriptions(Host, Node).
get_subscription(Host, Node, Owner) ->
node_default:get_subscription(Host, Node, Owner).
set_subscription(Host, Node, Owner, Subscription) ->
node_default:set_subscription(Host, Node, Owner, Subscription).
get_states(Host, Node) ->
node_default:get_states(Host, Node).
get_state(Host, Node, JID) ->
node_default:get_state(Host, Node, JID).
set_state(State) ->
node_default:set_state(State).
get_items(Host, Node) ->
node_default:get_items(Host, Node).
get_item(Host, Node, ItemId) ->
node_default:get_items(Host, Node, ItemId).
set_item(Item) ->
node_default:set_item(Item).

View File

@ -0,0 +1,164 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin.
%%% <p>It is used as a default for all unknown PubSub node type. It can serve
%%% as a developer basis and reference to build its own custom pubsub node tree
%%% types.</p>
%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p>
%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
%%% development is still a work in progress. However, the system is already
%%% useable and useful as is. Please, send us comments, feedback and
%%% improvements.</p>
-module(nodetree_default).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_nodetree).
-export([init/3,
terminate/2,
options/0,
set_node/1,
get_node/2,
get_nodes/1,
get_subnodes/2,
get_subnodes_tree/2,
create_node/5,
delete_node/2
]).
%% ================
%% API definition
%% ================
%% @spec (Host) -> any()
%% Host = mod_pubsub:host()
%% ServerHost = host()
%% Opts = list()
%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
%% implement this function. It can return anything.</p>
%% <p>This function is mainly used to trigger the setup task necessary for the
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
init(_Host, _ServerHost, _Opts) ->
mnesia:create_table(pubsub_node,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_node)},
{index, [type,parentid]}]),
NodesFields = record_info(fields, pubsub_node),
case mnesia:table_info(pubsub_node, attributes) of
NodesFields -> ok;
_ -> mnesia:transform_table(pubsub_node, ignore, NodesFields)
end,
ok.
terminate(_Host, _ServerHost) ->
ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodetreeOption()
%% @doc Returns the default pubsub node tree options.
options() ->
[{virtual_tree, false}].
%% @spec (NodeRecord) -> ok | {error, Reason}
%% Record = mod_pubsub:pubsub_node()
set_node(Record) when is_record(Record, pubsub_node) ->
mnesia:write(Record);
set_node(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}.
%% @spec (Host, Node) -> pubsubNode() | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
get_node(Host, Node) ->
case catch mnesia:read({pubsub_node, {Host, Node}}) of
[Record] when is_record(Record, pubsub_node) -> Record;
[] -> {error, ?ERR_ITEM_NOT_FOUND};
Error -> Error
end.
%% @spec (Key) -> [pubsubNode()] | {error, Reason}
%% Key = mod_pubsub:host() | mod_pubsub:jid()
get_nodes(Key) ->
mnesia:match_object(#pubsub_node{nodeid = {Key, '_'}, _ = '_'}).
%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
get_subnodes(Host, Node) ->
mnesia:index_read(pubsub_node, {Host, Node}, #pubsub_node.parentid).
%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
get_subnodes_tree(Host, Node) ->
mnesia:foldl(fun(#pubsub_node{nodeid = {H, N}}, Acc) ->
case lists:prefix(Node, N) and (H == Host) of
true -> [N | Acc];
_ -> Acc
end
end, [], pubsub_node).
%% @spec (Key, Node, Type, Owner, Options) -> ok | {error, Reason}
%% Key = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% NodeType = mod_pubsub:nodeType()
%% Owner = mod_pubsub:jid()
%% Options = list()
create_node(Key, Node, Type, Owner, Options) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
case mnesia:read({pubsub_node, {Key, Node}}) of
[] ->
Parent = lists:sublist(Node, length(Node) - 1),
ParentExists =
case Key of
{_U, _S, _R} ->
%% This is special case for PEP handling
%% PEP does not uses hierarchy
true;
_ ->
(Parent == []) orelse
case mnesia:read({pubsub_node, {Key, Parent}}) of
[] -> false;
_ -> true
end
end,
case ParentExists of
true ->
%% Service requires registration
%%{error, ?ERR_REGISTRATION_REQUIRED};
mnesia:write(#pubsub_node{nodeid = {Key, Node},
parentid = {Key, Parent},
type = Type,
owners = [OwnerKey],
options = Options});
false ->
%% Requesting entity is prohibited from creating nodes
{error, ?ERR_FORBIDDEN}
end;
_ ->
%% NodeID already exists
{error, ?ERR_CONFLICT}
end.
%% @spec (Key, Node) -> [mod_pubsub:node()]
%% Key = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
delete_node(Key, Node) ->
Removed = get_subnodes_tree(Key, Node),
lists:foreach(fun(N) ->
mnesia:delete({pubsub_node, {Key, N}})
end, Removed),
Removed.

View File

@ -0,0 +1,119 @@
%%% ====================================================================
%%% This software is copyright 2007, Process-one.
%%%
%%% @copyright 2007 Process-one
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @doc The module <strong>{@module}</strong> is the PubSub node tree plugin that
%%% allow virtual nodes handling.
%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p>
%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
%%% development is still a work in progress. However, the system is already
%%% useable and useful as is. Please, send us comments, feedback and
%%% improvements.</p>
-module(nodetree_virtual).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_nodetree).
-export([init/3,
terminate/2,
options/0,
set_node/1,
get_node/2,
get_nodes/1,
get_subnodes/2,
get_subnodes_tree/2,
create_node/5,
delete_node/2
]).
%% ================
%% API definition
%% ================
%% @spec (Host) -> any()
%% Host = mod_pubsub:host()
%% ServerHost = host()
%% Opts = list()
%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
%% implement this function. It can return anything.</p>
%% <p>This function is mainly used to trigger the setup task necessary for the
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
init(_Host, _ServerHost, _Opts) ->
ok.
terminate(_Host, _ServerHost) ->
ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodetreeOption()
%% @doc <p>Returns the default pubsub node tree options.</p>
options() ->
[{virtual_tree, true}].
%% @spec (NodeRecord) -> ok | {error, Reason}
%% NodeRecord = mod_pubsub:pubsub_node()
%% @doc <p>No node record is stored on database. Just do nothing.</p>
set_node(_NodeRecord) ->
ok.
%% @spec (Host, Node) -> pubsubNode()
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% @doc <p>Virtual node tree does not handle a node database. Any node is considered
%% as existing. Node record contains default values.</p>
get_node(Host, Node) ->
#pubsub_node{nodeid = {Host, Node}}.
%% @spec (Key) -> [pubsubNode()]
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% @doc <p>Virtual node tree does not handle a node database. Any node is considered
%% as existing. Nodes list can not be determined.</p>
get_nodes(_Key) ->
[].
%% @spec (Host, Index) -> [pubsubNode()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
get_subnodes(_Host, _Node) ->
[].
%% @spec (Host, Index) -> [pubsubNode()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
get_subnodes_tree(_Host, _Node) ->
[].
%% @spec (Host, Node, Type, Owner, Options) -> ok
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% Type = mod_pubsub:nodeType()
%% Owner = mod_pubsub:jid()
%% Options = list()
%% @doc <p>No node record is stored on database. Any valid node
%% is considered as already created.</p>
%% <p>default allowed nodes: /home/host/user/any/node/name</p>
create_node(_Host, Node, _Type, {UserName, UserHost, _}, _Options) ->
case Node of
["home", UserHost, UserName | _] -> {error, ?ERR_CONFLICT};
_ -> {error, ?ERR_NOT_ALLOWED}
end.
%% @spec (Host, Node) -> [mod_pubsub:node()]
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% @doc <p>Virtual node tree does not handle parent/child.
%% node deletion just affects the corresponding node.</p>
delete_node(_Host, Node) ->
[Node].

119
src/mod_pubsub/pubsub.hrl Normal file
View File

@ -0,0 +1,119 @@
%%% ====================================================================
%%% This software is copyright 2006, Process-one.
%%%
%%% This file contains pubsub types definition.
%%% ====================================================================
%% -------------------------------
%% Pubsub constants
-define(PUBSUB_JID, {jid, "", Host, "", "", Host, ""}).
-define(ERR_EXTENDED(E,C), mod_pubsub:extended_error(E,C)).
% TODO: move to jlib.hrl
-define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
-define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization").
%% this is currently a hard limit.
%% Would be nice to have it configurable.
-define(MAXITEMS, 20).
-define(MAX_PAYLOAD_SIZE, 60000).
%% -------------------------------
%% Pubsub types
%%% @type host() = string().
%%% <p><tt>host</tt> is the name of the PubSub service. For example, it can be
%%% <tt>"pubsub.localhost"</tt>.</p>
%%% @type pubsubNode() = [string()].
%%% <p>A node is defined by a list of its ancestors. The last element is the name
%%% of the current node. For example:
%%% ```["home", "localhost", "cromain", "node1"]'''</p>
%%% @type stanzaError() = #xmlelement{}.
%%% Example:
%%% ```{xmlelement, "error",
%%% [{"code", Code}, {"type", Type}],
%%% [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}'''
%%% @type pubsubIQResponse() = #xmlelement{}.
%%% Example:
%%% ```{xmlelement, "pubsub",
%%% [{"xmlns", ?NS_PUBSUB_EVENT}],
%%% [{xmlelement, "affiliations", [],
%%% []}]}'''
%%% @type nodeOption() = {Option::atom(), Value::term()}.
%%% Example:
%%% ```{deliver_payloads, true}'''
%%% @type nodeType() = string().
%%% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub
%%% plugin to use to manage a given node. For example, it can be
%%% <tt>"default"</tt>, <tt>"collection"</tt> or <tt>"blog"</tt>.</p>
%%% @type jid() = #jid{
%%% user = string(),
%%% server = string(),
%%% resource = string(),
%%% luser = string(),
%%% lserver = string(),
%%% lresource = string()}.
%%% @type usr() = {User::string(), Server::string(), Resource::string()}.
%%% @type affiliation() = none | owner | publisher | outcast.
%%% @type subscription() = none | pending | unconfigured | subscribed.
%%% @type pubsubNode() = #pubsub_node{
%%% nodeid = {Host::host(), Node::pubsubNode()},
%%% parentid = {Host::host(), Node::pubsubNode()},
%%% type = nodeType(),
%%% owners = [usr()],
%%% options = [nodeOption()]}.
%%% <p>This is the format of the <tt>nodes</tt> table. The type of the table
%%% is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
%%% <p>The <tt>parentid</tt> and <tt>type</tt> fields are indexed.</p>
-record(pubsub_node, {nodeid,
parentid = {},
type = "",
owners = [],
options = []
}).
%%% @type pubsubState() = #pubsub_state{
%%% stateid = {jid(), {Host::host(), Node::pubsubNode()}},
%%% items = [ItemId::string()],
%%% affiliation = affiliation(),
%%% subscription = subscription()}.
%%% <p>This is the format of the <tt>affiliations</tt> table. The type of the
%%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
-record(pubsub_state, {stateid,
items = [],
affiliation = none,
subscription = none
}).
%% @type pubsubItem() = #pubsub_item{
%% itemid = {ItemId::string(), {Host::host(),Node::pubsubNode()}},
%% creation = {JID::jid(), now()},
%% modification = {JID::jid(), now()},
%% payload = XMLContent::string()}.
%%% <p>This is the format of the <tt>published items</tt> table. The type of the
%%% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p>
-record(pubsub_item, {itemid,
creation = {unknown,unknown},
modification = {unknown,unknown},
payload = []
}).
%% @type pubsubPresence() = #pubsub_presence{
%% key = {Host::host(), User::string(), Server::string()},
%% presence = list().
%%% <p>This is the format of the <tt>published presence</tt> table. The type of the
%%% table is: <tt>set</tt>,<tt>ram</tt>.</p>
-record(pubsub_presence, {key,
resource
}).