mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-20 16:15:59 +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:
parent
e4cf286aa2
commit
c3c782d882
48
ChangeLog
48
ChangeLog
@ -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>
|
||||
|
||||
* src/odbc_queries.erl: Added a default define value so that we can
|
||||
recompile the file manually with a simple erlc command.
|
||||
* src/odbc_queries.erl: Added a default define value so that we
|
||||
can recompile the file manually with a simple erlc command.
|
||||
|
||||
2007-11-29 Badlop <badlop@process-one.net>
|
||||
|
||||
* src/mod_vcard.erl: Add type of x:data field to search
|
||||
results (thanks to Robin Redeker) (EJAB-327)
|
||||
* src/mod_vcard_ldap.erl:
|
||||
* src/mod_vcard_odbc.erl:
|
||||
* src/mod_vcard.erl: Add type of x:data field to search results
|
||||
(thanks to Robin Redeker) (EJAB-327)
|
||||
* src/mod_vcard_ldap.erl: Likewise
|
||||
* src/mod_vcard_odbc.erl: Likewise
|
||||
|
||||
* src/aclocal.m4: Fix autoconf caching for SSL libraries (thanks
|
||||
to Michael Shields) (EJAB-439)
|
||||
|
||||
* src/configure.ac: Don't hardcode gcc and gcc options in
|
||||
Makefiles (thanks to Etan Reisner) (EJAB-436)
|
||||
* src/Makefile.in:
|
||||
* src/ejabberd_zlib/Makefile.in:
|
||||
* src/eldap/Makefile.in:
|
||||
* src/mod_irc/Makefile.in:
|
||||
* src/mod_muc/Makefile.in:
|
||||
* src/mod_proxy65/Makefile.in:
|
||||
* src/mod_pubsub/Makefile.in:
|
||||
* src/odbc/Makefile.in:
|
||||
* src/pam/Makefile.in:
|
||||
* src/stringprep/Makefile.in:
|
||||
* src/tls/Makefile.in:
|
||||
* src/web/Makefile.in:
|
||||
* src/Makefile.in: Likewise
|
||||
* src/ejabberd_zlib/Makefile.in: Likewise
|
||||
* src/eldap/Makefile.in: Likewise
|
||||
* src/mod_irc/Makefile.in: Likewise
|
||||
* src/mod_muc/Makefile.in: Likewise
|
||||
* src/mod_proxy65/Makefile.in: Likewise
|
||||
* src/mod_pubsub/Makefile.in: Likewise
|
||||
* src/odbc/Makefile.in: Likewise
|
||||
* src/pam/Makefile.in: Likewise
|
||||
* src/stringprep/Makefile.in: Likewise
|
||||
* src/tls/Makefile.in: Likewise
|
||||
* src/web/Makefile.in: Likewise
|
||||
|
||||
* src/mod_muc/mod_muc_room.erl: Hide the option 'Make room
|
||||
moderated' because it isn't implemented, and set the default value
|
||||
|
@ -18,7 +18,8 @@
|
||||
send_text/2,
|
||||
send_element/2,
|
||||
socket_type/0,
|
||||
get_presence/1]).
|
||||
get_presence/1,
|
||||
get_subscribed_and_online/1]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1,
|
||||
@ -39,6 +40,7 @@
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(SETS, gb_sets).
|
||||
-define(DICT, dict).
|
||||
|
||||
-record(state, {socket,
|
||||
sockmod,
|
||||
@ -60,6 +62,7 @@
|
||||
pres_f = ?SETS:new(),
|
||||
pres_a = ?SETS:new(),
|
||||
pres_i = ?SETS:new(),
|
||||
pres_available = ?DICT:new(),
|
||||
pres_last, pres_pri,
|
||||
pres_timestamp,
|
||||
pres_invis = false,
|
||||
@ -173,6 +176,12 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
shaper = Shaper,
|
||||
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
|
||||
@ -572,7 +581,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
||||
case xml:get_subtag(El, "method") of
|
||||
false ->
|
||||
send_element(StateData,
|
||||
{xmlelement, "failure",
|
||||
{xmlelement, "failure",
|
||||
[{"xmlns", ?NS_COMPRESS}],
|
||||
[{xmlelement, "setup-failed", [], []}]}),
|
||||
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},
|
||||
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) ->
|
||||
Reply = ok,
|
||||
fsm_reply(Reply, StateName, StateData).
|
||||
@ -1054,32 +1074,42 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
allow ->
|
||||
LFrom = jlib:jid_tolower(From),
|
||||
LBFrom = jlib:jid_remove_resource(LFrom),
|
||||
%% Note contact availability
|
||||
Caps = mod_caps:read_caps(Els),
|
||||
mod_caps:note_caps(StateData#state.server, From, Caps),
|
||||
NewAvailable = case xml:get_attr_s("type", Attrs) of
|
||||
"unavailable" ->
|
||||
?DICT:erase(LFrom, StateData#state.pres_available);
|
||||
_ ->
|
||||
?DICT:store(LFrom, Caps, StateData#state.pres_available)
|
||||
end,
|
||||
NewStateData = StateData#state{pres_available = NewAvailable},
|
||||
case ?SETS:is_element(
|
||||
LFrom, StateData#state.pres_a) orelse
|
||||
LFrom, NewStateData#state.pres_a) orelse
|
||||
?SETS:is_element(
|
||||
LBFrom, StateData#state.pres_a) of
|
||||
LBFrom, NewStateData#state.pres_a) of
|
||||
true ->
|
||||
{true, Attrs, StateData};
|
||||
{true, Attrs, NewStateData};
|
||||
false ->
|
||||
case ?SETS:is_element(
|
||||
LFrom, StateData#state.pres_f) of
|
||||
LFrom, NewStateData#state.pres_f) of
|
||||
true ->
|
||||
A = ?SETS:add_element(
|
||||
LFrom,
|
||||
StateData#state.pres_a),
|
||||
NewStateData#state.pres_a),
|
||||
{true, Attrs,
|
||||
StateData#state{pres_a = A}};
|
||||
NewStateData#state{pres_a = A}};
|
||||
false ->
|
||||
case ?SETS:is_element(
|
||||
LBFrom, StateData#state.pres_f) of
|
||||
LBFrom, NewStateData#state.pres_f) of
|
||||
true ->
|
||||
A = ?SETS:add_element(
|
||||
LBFrom,
|
||||
StateData#state.pres_a),
|
||||
NewStateData#state.pres_a),
|
||||
{true, Attrs,
|
||||
StateData#state{pres_a = A}};
|
||||
NewStateData#state{pres_a = A}};
|
||||
false ->
|
||||
{true, Attrs, StateData}
|
||||
{true, Attrs, NewStateData}
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
@ -18,6 +18,7 @@
|
||||
-export([route/3,
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
register_iq_response_handler/4,
|
||||
unregister_iq_handler/2,
|
||||
refresh_iq_handlers/0,
|
||||
bounce_resource_packet/3
|
||||
@ -32,6 +33,8 @@
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
-record(iq_response, {id, module, function}).
|
||||
|
||||
-define(IQTABLE, local_iqtable).
|
||||
|
||||
%%====================================================================
|
||||
@ -68,13 +71,38 @@ process_iq(From, To, Packet) ->
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
reply ->
|
||||
ok;
|
||||
process_iq_reply(From, To, Packet);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
ok
|
||||
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) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
@ -84,6 +112,9 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
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) ->
|
||||
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
|
||||
@ -120,6 +151,9 @@ init([]) ->
|
||||
?MODULE, bounce_resource_packet, 100)
|
||||
end, ?MYHOSTS),
|
||||
catch ets:new(?IQTABLE, [named_table, public]),
|
||||
mnesia:create_table(iq_response,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, iq_response)}]),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@ -159,6 +193,9 @@ handle_info({route, From, To, Packet}, State) ->
|
||||
ok
|
||||
end,
|
||||
{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) ->
|
||||
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
|
||||
catch mod_disco:register_feature(Host, XMLNS),
|
||||
|
@ -28,6 +28,7 @@
|
||||
register_iq_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
ctl_process/2,
|
||||
get_session_pid/3,
|
||||
get_user_ip/3
|
||||
]).
|
||||
|
||||
@ -74,7 +75,7 @@ open_session(SID, User, Server, Resource, IP) ->
|
||||
close_session(SID, User, Server, Resource) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({session, SID})
|
||||
end,
|
||||
end,
|
||||
mnesia:sync_dirty(F),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
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),
|
||||
[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() ->
|
||||
mnesia:dirty_select(
|
||||
@ -315,7 +325,7 @@ clean_table_from_bad_node(Node) ->
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete({session, E#session.sid})
|
||||
end, Es)
|
||||
end,
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
@ -368,7 +378,6 @@ do_route(From, To, Packet) ->
|
||||
{true, false}
|
||||
end,
|
||||
if Pass ->
|
||||
LFrom = jlib:jid_tolower(From),
|
||||
PResources = get_user_present_resources(
|
||||
LUser, LServer),
|
||||
lists:foreach(
|
||||
@ -377,7 +386,9 @@ do_route(From, To, Packet) ->
|
||||
From,
|
||||
jlib:jid_replace_resource(To, R),
|
||||
Packet)
|
||||
end, PResources);
|
||||
end, PResources),
|
||||
ejabberd_hooks:run(incoming_presence_hook, LServer,
|
||||
[From, To, Packet]);
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
@ -649,4 +660,3 @@ update_tables() ->
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
|
72
src/jlib.erl
72
src/jlib.erl
@ -32,6 +32,7 @@
|
||||
jid_replace_resource/2,
|
||||
get_iq_namespace/1,
|
||||
iq_query_info/1,
|
||||
iq_query_or_response_info/1,
|
||||
is_iq_request_type/1,
|
||||
iq_to_xml/1,
|
||||
parse_xdata_submit/1,
|
||||
@ -331,39 +332,66 @@ get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
|
||||
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),
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
Type1 = case Type of
|
||||
"set" -> set;
|
||||
"get" -> get;
|
||||
"result" -> reply;
|
||||
"error" -> reply;
|
||||
_ -> invalid
|
||||
{Type1, Class} = case Type of
|
||||
"set" -> {set, request};
|
||||
"get" -> {get, request};
|
||||
"result" -> {result, reply};
|
||||
"error" -> {error, reply};
|
||||
_ -> {invalid, invalid}
|
||||
end,
|
||||
if
|
||||
(Type1 /= invalid) and (Type1 /= reply) ->
|
||||
case xml:remove_cdata(Els) of
|
||||
[{xmlelement, Name2, Attrs2, Els2}] ->
|
||||
XMLNS = xml:get_attr_s("xmlns", Attrs2),
|
||||
if
|
||||
XMLNS /= "" ->
|
||||
Type1 == invalid ->
|
||||
invalid;
|
||||
Class == request; Filter == any ->
|
||||
%% The iq record is a bit strange. The sub_el field is an
|
||||
%% XML tuple for requests, but a list of XML tuples for
|
||||
%% 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,
|
||||
type = Type1,
|
||||
xmlns = XMLNS,
|
||||
lang = Lang,
|
||||
sub_el = {xmlelement, Name2, Attrs2, Els2}};
|
||||
true ->
|
||||
invalid
|
||||
sub_el = SubEl}
|
||||
end;
|
||||
_ ->
|
||||
invalid
|
||||
end;
|
||||
true ->
|
||||
Type1
|
||||
Class == reply, Filter /= any ->
|
||||
reply
|
||||
end;
|
||||
iq_query_info(_) ->
|
||||
iq_info_internal(_, _) ->
|
||||
not_iq.
|
||||
|
||||
is_iq_request_type(set) -> true;
|
||||
|
@ -33,6 +33,7 @@
|
||||
-define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
|
||||
-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_ERRORS,"http://jabber.org/protocol/pubsub#errors").
|
||||
-define(NS_COMMANDS, "http://jabber.org/protocol/commands").
|
||||
-define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams").
|
||||
-define(NS_ADMIN, "http://jabber.org/protocol/admin").
|
||||
@ -55,6 +56,8 @@
|
||||
|
||||
-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)
|
||||
-define(STANZA_ERROR(Code, Type, Condition),
|
||||
{xmlelement, "error",
|
||||
|
259
src/mod_caps.erl
Normal file
259
src/mod_caps.erl
Normal 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}.
|
@ -15,11 +15,18 @@ OUTDIR = ..
|
||||
EFLAGS = -I .. -pz ..
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info
|
||||
EFLAGS+=+debug_info
|
||||
endif
|
||||
|
||||
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)
|
||||
|
||||
|
@ -5,12 +5,38 @@ OUTDIR = ..
|
||||
EFLAGS = -I .. -pz ..
|
||||
|
||||
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)
|
||||
|
||||
CLEAN :
|
||||
-@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
|
||||
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
|
||||
|
||||
|
55
src/mod_pubsub/gen_pubsub_node.erl
Normal file
55
src/mod_pubsub/gen_pubsub_node.erl
Normal 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.
|
40
src/mod_pubsub/gen_pubsub_nodetree.erl
Normal file
40
src/mod_pubsub/gen_pubsub_nodetree.erl
Normal 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
163
src/mod_pubsub/node.template
Normal file
163
src/mod_pubsub/node.template
Normal 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).
|
163
src/mod_pubsub/node_buddy.erl
Normal file
163
src/mod_pubsub/node_buddy.erl
Normal 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).
|
163
src/mod_pubsub/node_club.erl
Normal file
163
src/mod_pubsub/node_club.erl
Normal 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).
|
712
src/mod_pubsub/node_default.erl
Normal file
712
src/mod_pubsub/node_default.erl
Normal 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}.
|
166
src/mod_pubsub/node_dispatch.erl
Normal file
166
src/mod_pubsub/node_dispatch.erl
Normal 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
173
src/mod_pubsub/node_pep.erl
Normal 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).
|
166
src/mod_pubsub/node_private.erl
Normal file
166
src/mod_pubsub/node_private.erl
Normal 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).
|
165
src/mod_pubsub/node_public.erl
Normal file
165
src/mod_pubsub/node_public.erl
Normal 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).
|
164
src/mod_pubsub/nodetree_default.erl
Normal file
164
src/mod_pubsub/nodetree_default.erl
Normal 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.
|
119
src/mod_pubsub/nodetree_virtual.erl
Normal file
119
src/mod_pubsub/nodetree_virtual.erl
Normal 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
119
src/mod_pubsub/pubsub.hrl
Normal 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
|
||||
}).
|
||||
|
Loading…
Reference in New Issue
Block a user