mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-20 17:27:00 +01:00
experimental patch including XEP-248 (thanks to Brian Cully)
SVN Revision: 2157
This commit is contained in:
parent
6f080f7fed
commit
50b73664e2
@ -4,7 +4,7 @@ include ..\Makefile.inc
|
||||
EFLAGS = -I .. -pz ..
|
||||
|
||||
OUTDIR = ..
|
||||
BEAMS = ..\gen_pubsub_node.beam ..\gen_pubsub_nodetree.beam ..\mod_pubsub.beam ..\nodetree_default.beam ..\nodetree_virtual.beam ..\node_buddy.beam ..\node_club.beam ..\node_default.beam ..\node_dispatch.beam ..\node_pep.beam ..\node_private.beam ..\node_public.beam
|
||||
BEAMS = ..\gen_pubsub_node.beam ..\gen_pubsub_nodetree.beam ..\mod_pubsub.beam ..\nodetree_tree.beam ..\nodetree_virtual.beam ..\node_buddy.beam ..\node_club.beam ..\node_hometree.beam ..\node_dispatch.beam ..\node_pep.beam ..\node_private.beam ..\node_public.beam
|
||||
|
||||
ALL : $(BEAMS)
|
||||
|
||||
@ -20,8 +20,8 @@ $(OUTDIR)\gen_pubsub_nodetree.beam : 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_tree.beam : nodetree_tree.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_tree.erl
|
||||
|
||||
$(OUTDIR)\nodetree_virtual.beam : nodetree_virtual.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) nodetree_virtual.erl
|
||||
@ -32,8 +32,8 @@ $(OUTDIR)\node_buddy.beam : node_buddy.erl
|
||||
$(OUTDIR)\node_club.beam : node_club.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) node_club.erl
|
||||
|
||||
$(OUTDIR)\node_default.beam : node_default.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) node_default.erl
|
||||
$(OUTDIR)\node_hometree.beam : node_hometree.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) node_hometree.erl
|
||||
|
||||
$(OUTDIR)\node_dispatch.beam : node_dispatch.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) node_dispatch.erl
|
||||
|
@ -47,6 +47,8 @@ behaviour_info(callbacks) ->
|
||||
{get_node, 1},
|
||||
{get_nodes, 2},
|
||||
{get_nodes, 1},
|
||||
{get_parentnodes, 3},
|
||||
{get_parentnodes_tree, 3},
|
||||
{get_subnodes, 3},
|
||||
{get_subnodes_tree, 3},
|
||||
{create_node, 5},
|
||||
|
@ -80,7 +80,7 @@
|
||||
get_items/2,
|
||||
get_item/3,
|
||||
get_cached_item/2,
|
||||
broadcast_stanza/7,
|
||||
broadcast_stanza/8,
|
||||
get_configure/5,
|
||||
set_configure/5,
|
||||
tree_action/3,
|
||||
@ -304,7 +304,7 @@ update_node_database(Host, ServerHost) ->
|
||||
mnesia:delete({pubsub_node, NodeId}),
|
||||
{[#pubsub_node{nodeid = NodeId,
|
||||
id = NodeIdx,
|
||||
parent = element(2, ParentId),
|
||||
parents = [element(2, ParentId)],
|
||||
owners = Owners,
|
||||
options = Options} |
|
||||
RecList], NodeIdx + 1}
|
||||
@ -334,12 +334,12 @@ update_node_database(Host, ServerHost) ->
|
||||
#pubsub_node{
|
||||
nodeid = NodeId,
|
||||
id = 0,
|
||||
parent = Parent,
|
||||
parents = [Parent],
|
||||
type = Type,
|
||||
owners = Owners,
|
||||
options = Options}
|
||||
end,
|
||||
mnesia:transform_table(pubsub_node, F, [nodeid, id, parent, type, owners, options]),
|
||||
mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
|
||||
FNew = fun() ->
|
||||
lists:foldl(fun(#pubsub_node{nodeid = NodeId} = PubsubNode, NodeIdx) ->
|
||||
mnesia:write(PubsubNode#pubsub_node{id = NodeIdx}),
|
||||
@ -371,6 +371,17 @@ update_node_database(Host, ServerHost) ->
|
||||
?ERROR_MSG("Problem updating Pubsub node tables:~n~p",
|
||||
[Reason])
|
||||
end;
|
||||
[nodeid, id, parent, type, owners, options] ->
|
||||
F = fun({pubsub_node, NodeId, Id, Parent, Type, Owners, Options}) ->
|
||||
#pubsub_node{
|
||||
nodeid = NodeId,
|
||||
id = Id,
|
||||
parents = [Parent],
|
||||
type = Type,
|
||||
owners = Owners,
|
||||
options = Options}
|
||||
end,
|
||||
mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
@ -1443,6 +1454,12 @@ replace_subscription_helper(_, OldSub, Acc) ->
|
||||
-define(STRINGXFIELD(Label, Var, Val),
|
||||
?XFIELD("text-single", Label, Var, Val)).
|
||||
|
||||
-define(STRINGMXFIELD(Label, Var, Vals),
|
||||
{xmlelement, "field", [{"type", "text-multi"},
|
||||
{"label", translate:translate(Lang, Label)},
|
||||
{"var", Var}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, V}]} || V <- Vals]}).
|
||||
|
||||
-define(XFIELDOPT(Type, Label, Var, Val, Opts),
|
||||
{xmlelement, "field", [{"type", Type},
|
||||
{"label", translate:translate(Lang, Label)},
|
||||
@ -1601,10 +1618,14 @@ delete_node(_Host, [], _Owner) ->
|
||||
{error, ?ERR_NOT_ALLOWED};
|
||||
delete_node(Host, Node, Owner) ->
|
||||
Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
SubsByDepth = get_collection_subscriptions(Host, Node),
|
||||
case node_call(Type, get_affiliation, [NodeId, Owner]) of
|
||||
{result, owner} ->
|
||||
Removed = tree_call(Host, delete_node, [Host, Node]),
|
||||
node_call(Type, delete_node, [Removed]);
|
||||
case node_call(Type, delete_node, [Removed]) of
|
||||
{result, Res} -> {result, {SubsByDepth, Res}};
|
||||
Error -> Error
|
||||
end;
|
||||
_ ->
|
||||
%% Entity is not an owner
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
@ -1612,27 +1633,26 @@ delete_node(Host, Node, Owner) ->
|
||||
end,
|
||||
Reply = [],
|
||||
case transaction(Host, Node, Action, transaction) of
|
||||
{result, {_, {Result, broadcast, Removed}}} ->
|
||||
lists:foreach(fun({RNode, RSubscriptions}) ->
|
||||
{result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} ->
|
||||
lists:foreach(fun({RNode, _RSubscriptions}) ->
|
||||
{RH, RN} = RNode#pubsub_node.nodeid,
|
||||
NodeId = RNode#pubsub_node.id,
|
||||
Type = RNode#pubsub_node.type,
|
||||
Options = RNode#pubsub_node.options,
|
||||
broadcast_removed_node(RH, RN, NodeId, Type, Options, RSubscriptions),
|
||||
unset_cached_item(RH, NodeId)
|
||||
broadcast_removed_node(RH, RN, NodeId, Type, Options, SubsByDepth)
|
||||
end, Removed),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_, {Result, _Removed}}} ->
|
||||
{result, {_, {_, {Result, _Removed}}}} ->
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_, default}} ->
|
||||
{result, {_, {_, default}}} ->
|
||||
{result, Reply};
|
||||
{result, {_, Result}} ->
|
||||
{result, {_, {_, Result}}} ->
|
||||
{result, Result};
|
||||
Error ->
|
||||
Error
|
||||
@ -2605,19 +2625,28 @@ service_jid(Host) ->
|
||||
|
||||
%% @spec (LJID, PresenceDelivery) -> boolean()
|
||||
%% LJID = jid()
|
||||
%% NotifyType = items | nodes
|
||||
%% Depth = integer()
|
||||
%% NodeOptions = [{atom(), term()}]
|
||||
%% SubOptions = [{atom(), term()}]
|
||||
%% @doc <p>Check if a notification must be delivered or not based on
|
||||
%% node and subscription options.</p>
|
||||
is_to_deliver(LJID, NodeOptions, SubOptions) ->
|
||||
sub_to_deliver(LJID, SubOptions) andalso node_to_deliver(LJID, NodeOptions).
|
||||
is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) ->
|
||||
sub_to_deliver(LJID, NotifyType, Depth, SubOptions)
|
||||
andalso node_to_deliver(LJID, NodeOptions).
|
||||
|
||||
sub_to_deliver(_LJID, SubOptions) ->
|
||||
lists:all(fun sub_option_can_deliver/1, SubOptions).
|
||||
sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) ->
|
||||
lists:all(fun (Option) ->
|
||||
sub_option_can_deliver(NotifyType, Depth, Option)
|
||||
end, SubOptions).
|
||||
|
||||
sub_option_can_deliver({deliver, false}) -> false;
|
||||
sub_option_can_deliver({expire, When}) -> now() < When;
|
||||
sub_option_can_deliver(_) -> true.
|
||||
sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false;
|
||||
sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false;
|
||||
sub_option_can_deliver(_, _, {subscription_depth, all}) -> true;
|
||||
sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth < D;
|
||||
sub_option_can_deliver(_, _, {deliver, false}) -> false;
|
||||
sub_option_can_deliver(_, _, {expire, When}) -> now() < When;
|
||||
sub_option_can_deliver(_, _, _) -> true.
|
||||
|
||||
node_to_deliver(LJID, NodeOptions) ->
|
||||
PresenceDelivery = get_option(NodeOptions, presence_based_delivery),
|
||||
@ -2652,11 +2681,8 @@ event_stanza(Els) ->
|
||||
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, _From, Payload) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, none, true, "items", ItemEls)
|
||||
case node_action(Host, Type, get_node_subscriptions, [NodeId]) of
|
||||
{result, []} ->
|
||||
{result, false};
|
||||
{result, Subs} ->
|
||||
SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
SubsByDepth when is_list(SubsByDepth) ->
|
||||
Content = case get_option(NodeOptions, deliver_payloads) of
|
||||
true -> Payload;
|
||||
false -> []
|
||||
@ -2665,7 +2691,7 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, _
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
[{xmlelement, "item", itemAttr(ItemId), Content}]}]),
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubOptions, Stanza),
|
||||
NodeOptions, SubsByDepth, items, Stanza),
|
||||
case Removed of
|
||||
[] ->
|
||||
ok;
|
||||
@ -2676,8 +2702,8 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, _
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
[{xmlelement, "retract", itemAttr(RId), []} || RId <- Removed]}]),
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubOptions,
|
||||
RetractStanza);
|
||||
NodeOptions, SubsByDepth,
|
||||
items, RetractStanza);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
@ -2695,16 +2721,13 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNot
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_retract, ForceNotify, "retract", RetractEls)
|
||||
case (get_option(NodeOptions, notify_retract) or ForceNotify) of
|
||||
true ->
|
||||
case node_action(Host, Type, get_node_subscriptions, [NodeId]) of
|
||||
{result, []} ->
|
||||
{result, false};
|
||||
{result, Subs} ->
|
||||
SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
SubsByDepth when is_list(SubsByDepth) ->
|
||||
Stanza = event_stanza(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
[{xmlelement, "retract", itemAttr(ItemId), []} || ItemId <- ItemIds]}]),
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubOptions, Stanza),
|
||||
NodeOptions, SubsByDepth, items, Stanza),
|
||||
{result, true};
|
||||
_ ->
|
||||
{result, false}
|
||||
@ -2717,16 +2740,13 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_retract, false, "purge", [])
|
||||
case get_option(NodeOptions, notify_retract) of
|
||||
true ->
|
||||
case node_action(Host, Type, get_node_subscriptions, [NodeId]) of
|
||||
{result, []} ->
|
||||
{result, false};
|
||||
{result, Subs} ->
|
||||
SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
SubsByDepth when is_list(SubsByDepth) ->
|
||||
Stanza = event_stanza(
|
||||
[{xmlelement, "purge", nodeAttr(Node),
|
||||
[]}]),
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubOptions, Stanza),
|
||||
NodeOptions, SubsByDepth, nodes, Stanza),
|
||||
{result, true};
|
||||
_ ->
|
||||
{result, false}
|
||||
@ -2735,20 +2755,19 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
|
||||
{result, false}
|
||||
end.
|
||||
|
||||
broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, Subs) ->
|
||||
broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_delete, false, "delete", [])
|
||||
case get_option(NodeOptions, notify_delete) of
|
||||
true ->
|
||||
case Subs of
|
||||
case SubsByDepth of
|
||||
[] ->
|
||||
{result, false};
|
||||
_ ->
|
||||
SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
|
||||
Stanza = event_stanza(
|
||||
[{xmlelement, "delete", nodeAttr(Node),
|
||||
[]}]),
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubOptions, Stanza),
|
||||
NodeOptions, SubsByDepth, nodes, Stanza),
|
||||
{result, true}
|
||||
end;
|
||||
_ ->
|
||||
@ -2759,11 +2778,8 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_config, false, "items", ConfigEls)
|
||||
case get_option(NodeOptions, notify_config) of
|
||||
true ->
|
||||
case node_action(Host, Type, get_node_subscriptions, [NodeId]) of
|
||||
{result, []} ->
|
||||
{result, false};
|
||||
{result, Subs} ->
|
||||
SubOptions = get_options_for_subs(Host, Node, NodeId, Subs),
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
SubsByDepth when is_list(SubsByDepth) ->
|
||||
Content = case get_option(NodeOptions, deliver_payloads) of
|
||||
true ->
|
||||
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
@ -2775,7 +2791,7 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
[{xmlelement, "item", itemAttr("configuration"), Content}]}]),
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubOptions, Stanza),
|
||||
NodeOptions, SubsByDepth, nodes, Stanza),
|
||||
{result, true};
|
||||
_ ->
|
||||
{result, false}
|
||||
@ -2784,6 +2800,28 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
|
||||
{result, false}
|
||||
end.
|
||||
|
||||
get_collection_subscriptions(Host, Node) ->
|
||||
case mnesia:transaction(fun tree_call/3,
|
||||
[Host, get_parentnodes_tree,
|
||||
[Host, Node, service_jid(Host)]]) of
|
||||
{atomic, NodesByDepth} when is_list(NodesByDepth) ->
|
||||
lists:map(fun ({Depth, Nodes}) ->
|
||||
{Depth, [{N, get_node_subs(N)} || N <- Nodes]}
|
||||
end, NodesByDepth);
|
||||
Other ->
|
||||
Other
|
||||
end.
|
||||
|
||||
get_node_subs(#pubsub_node{type = Type,
|
||||
nodeid = {Host, Node},
|
||||
id = NodeID}) ->
|
||||
case node_action(Host, Type, get_node_subscriptions, [NodeID]) of
|
||||
{result, Subs} ->
|
||||
get_options_for_subs(Host, Node, NodeID, Subs);
|
||||
Other ->
|
||||
Other
|
||||
end.
|
||||
|
||||
% TODO: merge broadcast code that way
|
||||
%broadcast(Host, Node, NodeId, Type, NodeOptions, Feature, Force, ElName, SubEls) ->
|
||||
% case (get_option(NodeOptions, Feature) or Force) of
|
||||
@ -2802,15 +2840,14 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
|
||||
% {result, false}
|
||||
% end
|
||||
|
||||
broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, Subs, Stanza) ->
|
||||
broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, Stanza) ->
|
||||
%AccessModel = get_option(NodeOptions, access_model),
|
||||
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
|
||||
From = service_jid(Host),
|
||||
%% Handles explicit subscriptions
|
||||
DeliverSubs = lists:filter(fun({LJID, _Node, SubOptions}) ->
|
||||
is_to_deliver(LJID, NodeOptions, SubOptions)
|
||||
end, Subs),
|
||||
lists:foreach(fun({LJID, _Node, _SubOptions}) ->
|
||||
FilteredSubsByDepth = depths_to_deliver(NotifyType, SubsByDepth),
|
||||
NodesByJID = collate_subs_by_jid(FilteredSubsByDepth),
|
||||
lists:foreach(fun ({LJID, Nodes}) ->
|
||||
LJIDs = case BroadcastAll of
|
||||
true ->
|
||||
{U, S, _} = LJID,
|
||||
@ -2818,10 +2855,11 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, Subs, Stanza) ->
|
||||
false ->
|
||||
[LJID]
|
||||
end,
|
||||
SHIMStanza = add_headers(Stanza, collection_shim(Node, Nodes)),
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router ! {route, From, jlib:make_jid(To), Stanza}
|
||||
ejabberd_router ! {route, From, jlib:make_jid(To), SHIMStanza}
|
||||
end, LJIDs)
|
||||
end, DeliverSubs),
|
||||
end, NodesByJID),
|
||||
%% Handles implicit presence subscriptions
|
||||
case Host of
|
||||
{LUser, LServer, LResource} ->
|
||||
@ -2869,6 +2907,37 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, Subs, Stanza) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
depths_to_deliver(NotifyType, SubsByDepth) ->
|
||||
NodesToDeliver =
|
||||
fun (Depth, Node, Subs, Acc) ->
|
||||
lists:foldl(fun ({LJID, _Node, SubOptions} = S, Acc2) ->
|
||||
case is_to_deliver(LJID, NotifyType, Depth,
|
||||
Node#pubsub_node.options,
|
||||
SubOptions) of
|
||||
true -> [S | Acc2];
|
||||
false -> Acc2
|
||||
end
|
||||
end, Acc, Subs)
|
||||
end,
|
||||
|
||||
DepthsToDeliver =
|
||||
fun ({Depth, SubsByNode}, Acc) ->
|
||||
lists:foldl(fun ({Node, Subs}, Acc2) ->
|
||||
NodesToDeliver(Depth, Node, Subs, Acc2)
|
||||
end, Acc, SubsByNode)
|
||||
end,
|
||||
|
||||
lists:foldl(DepthsToDeliver, [], SubsByDepth).
|
||||
|
||||
collate_subs_by_jid(SubsByDepth) ->
|
||||
lists:foldl(fun ({JID, Node, _Options}, Acc) ->
|
||||
OldNodes = case lists:keysearch(JID, 1, Acc) of
|
||||
{value, {JID, Nodes}} -> Nodes;
|
||||
false -> []
|
||||
end,
|
||||
lists:keystore(JID, 1, Acc, {JID, [Node | OldNodes]})
|
||||
end, [], SubsByDepth).
|
||||
|
||||
%% If we don't know the resource, just pick first if any
|
||||
%% If no resource available, check if caps anyway (remote online)
|
||||
user_resources(User, Server) ->
|
||||
@ -3000,6 +3069,10 @@ max_items(Options) ->
|
||||
?LISTMXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
|
||||
get_option(Options, Var), Opts)).
|
||||
|
||||
-define(NLIST_CONFIG_FIELD(Label, Var),
|
||||
?STRINGMXFIELD(Label, "pubsub#" ++ atom_to_list(Var),
|
||||
[node_to_string(N) || N <- get_option(Options, Var)])).
|
||||
|
||||
get_configure_xfields(_Type, Options, Lang, Groups) ->
|
||||
[?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NODE_CONFIG),
|
||||
?BOOL_CONFIG_FIELD("Deliver payloads with event notifications", deliver_payloads),
|
||||
@ -3020,7 +3093,8 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
|
||||
?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
|
||||
?ALIST_CONFIG_FIELD("When to send the last published item", send_last_published_item,
|
||||
[never, on_sub, on_sub_and_presence]),
|
||||
?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery)
|
||||
?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery),
|
||||
?NLIST_CONFIG_FIELD("The collections with which a node is affiliated", collection)
|
||||
].
|
||||
|
||||
%%<p>There are several reasons why the node configuration request might fail:</p>
|
||||
@ -3052,8 +3126,10 @@ set_configure(Host, Node, From, Els, Lang) ->
|
||||
end,
|
||||
case set_xoption(XData, OldOpts) of
|
||||
NewOpts when is_list(NewOpts) ->
|
||||
tree_call(Host, set_node, [N#pubsub_node{options = NewOpts}]),
|
||||
{result, ok};
|
||||
case tree_call(Host, set_node, [N#pubsub_node{options = NewOpts}]) of
|
||||
ok -> {result, ok};
|
||||
Err -> Err
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
@ -3166,6 +3242,9 @@ set_xoption([{"pubsub#type", Value} | Opts], NewOpts) ->
|
||||
?SET_STRING_XOPT(type, Value);
|
||||
set_xoption([{"pubsub#body_xslt", Value} | Opts], NewOpts) ->
|
||||
?SET_STRING_XOPT(body_xslt, Value);
|
||||
set_xoption([{"pubsub#collection", Value} | Opts], NewOpts) ->
|
||||
NewValue = [string_to_node(V) || V <- Value],
|
||||
?SET_LIST_XOPT(collection, NewValue);
|
||||
set_xoption([_ | Opts], NewOpts) ->
|
||||
% skip unknown field
|
||||
set_xoption(Opts, NewOpts).
|
||||
@ -3257,7 +3336,7 @@ features() ->
|
||||
"member-affiliation", % RECOMMENDED
|
||||
%TODO "meta-data", % RECOMMENDED
|
||||
% see plugin "modify-affiliations", % OPTIONAL
|
||||
%TODO "multi-collection", % OPTIONAL
|
||||
% see plugin "multi-collection", % OPTIONAL
|
||||
% see plugin "multi-subscribe", % OPTIONAL
|
||||
% see plugin "outcast-affiliation", % RECOMMENDED
|
||||
% see plugin "persistent-items", % RECOMMENDED
|
||||
@ -3395,3 +3474,10 @@ itemsEls(Items) ->
|
||||
lists:map(fun(#pubsub_item{itemid = {ItemId, _}, payload = Payload}) ->
|
||||
{xmlelement, "item", itemAttr(ItemId), Payload}
|
||||
end, Items).
|
||||
|
||||
add_headers({xmlelement, Name, Attrs, Els}, Headers) ->
|
||||
{xmlelement, Name, Attrs, Els ++ Headers}.
|
||||
|
||||
collection_shim(Node, Nodes) ->
|
||||
[{xmlelement, "header", [{"name", "Collection"}],
|
||||
[{xmlcdata, node_to_string(N)}]} || N <- Nodes -- [Node]].
|
||||
|
@ -65,7 +65,8 @@
|
||||
get_items/2,
|
||||
get_item/7,
|
||||
get_item/2,
|
||||
set_item/1
|
||||
set_item/1,
|
||||
get_item_name/3
|
||||
]).
|
||||
|
||||
|
||||
@ -76,8 +77,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, __TO_BE_DEFINED__},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -118,8 +118,8 @@ create_node(NodeId, Owner) ->
|
||||
delete_node(Removed) ->
|
||||
node_hometree:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
|
||||
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
|
||||
|
||||
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
|
||||
node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
|
||||
@ -158,7 +158,7 @@ get_subscriptions(NodeId, Owner) ->
|
||||
node_hometree:get_subscriptions(NodeId, Owner).
|
||||
|
||||
set_subscriptions(NodeId, Owner, Subscriptions) ->
|
||||
node_hometree:set_subscriptions(NodeId, Owner, Subscription).
|
||||
node_hometree:set_subscriptions(NodeId, Owner, Subscriptions).
|
||||
|
||||
get_states(NodeId) ->
|
||||
node_hometree:get_states(NodeId).
|
||||
@ -172,7 +172,7 @@ set_state(State) ->
|
||||
get_items(NodeId, From) ->
|
||||
node_hometree:get_items(NodeId, From).
|
||||
|
||||
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId)
|
||||
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
|
||||
node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
|
||||
|
||||
get_item(NodeId, ItemId) ->
|
||||
@ -183,3 +183,6 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, Su
|
||||
|
||||
set_item(Item) ->
|
||||
node_hometree:set_item(Item).
|
||||
|
||||
get_item_name(Host, Node, Id) ->
|
||||
node_hometree:get_item_name(Host, Node, Id).
|
||||
|
@ -78,8 +78,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, buddy},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
|
@ -78,8 +78,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, club},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
|
154
src/mod_pubsub/node_dag.erl
Normal file
154
src/mod_pubsub/node_dag.erl
Normal file
@ -0,0 +1,154 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% @author Brian Cully <bjc@kublai.com>
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
-module(node_dag).
|
||||
-author('bjc@kublai.com').
|
||||
|
||||
-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/2,
|
||||
delete_node/1,
|
||||
purge_node/2,
|
||||
subscribe_node/8,
|
||||
unsubscribe_node/4,
|
||||
publish_item/6,
|
||||
delete_item/4,
|
||||
remove_extra_items/3,
|
||||
get_entity_affiliations/2,
|
||||
get_node_affiliations/1,
|
||||
get_affiliation/2,
|
||||
set_affiliation/3,
|
||||
get_entity_subscriptions/2,
|
||||
get_node_subscriptions/1,
|
||||
get_subscriptions/2,
|
||||
set_subscriptions/3,
|
||||
get_states/1,
|
||||
get_state/2,
|
||||
set_state/1,
|
||||
get_items/6,
|
||||
get_items/2,
|
||||
get_item/7,
|
||||
get_item/2,
|
||||
set_item/1,
|
||||
get_item_name/3]).
|
||||
|
||||
|
||||
init(Host, ServerHost, Opts) ->
|
||||
node_hometree:init(Host, ServerHost, Opts).
|
||||
|
||||
terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, leaf} | node_hometree:options()].
|
||||
|
||||
features() ->
|
||||
["multi-collection" | node_hometree:features()].
|
||||
|
||||
create_node_permission(_Host, _ServerHost, _Node, _ParentNode,
|
||||
_Owner, _Access) ->
|
||||
{result, true}.
|
||||
|
||||
create_node(NodeID, Owner) ->
|
||||
node_hometree:create_node(NodeID, Owner).
|
||||
|
||||
delete_node(Removed) ->
|
||||
node_hometree:delete_node(Removed).
|
||||
|
||||
subscribe_node(NodeID, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
node_hometree:subscribe_node(NodeID, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup,
|
||||
Options).
|
||||
|
||||
unsubscribe_node(NodeID, Sender, Subscriber, SubID) ->
|
||||
node_hometree:unsubscribe_node(NodeID, Sender, Subscriber, SubID).
|
||||
|
||||
publish_item(NodeID, Publisher, Model, MaxItems, ItemID, Payload) ->
|
||||
node_hometree:publish_item(NodeID, Publisher, Model, MaxItems,
|
||||
ItemID, Payload).
|
||||
|
||||
remove_extra_items(NodeID, MaxItems, ItemIDs) ->
|
||||
node_hometree:remove_extra_items(NodeID, MaxItems, ItemIDs).
|
||||
|
||||
delete_item(NodeID, Publisher, PublishModel, ItemID) ->
|
||||
node_hometree:delete_item(NodeID, Publisher, PublishModel, ItemID).
|
||||
|
||||
purge_node(NodeID, Owner) ->
|
||||
node_hometree:purge_node(NodeID, Owner).
|
||||
|
||||
get_entity_affiliations(Host, Owner) ->
|
||||
node_hometree:get_entity_affiliations(Host, Owner).
|
||||
|
||||
get_node_affiliations(NodeID) ->
|
||||
node_hometree:get_node_affiliations(NodeID).
|
||||
|
||||
get_affiliation(NodeID, Owner) ->
|
||||
node_hometree:get_affiliation(NodeID, Owner).
|
||||
|
||||
set_affiliation(NodeID, Owner, Affiliation) ->
|
||||
node_hometree:set_affiliation(NodeID, Owner, Affiliation).
|
||||
|
||||
get_entity_subscriptions(Host, Owner) ->
|
||||
node_hometree:get_entity_subscriptions(Host, Owner).
|
||||
|
||||
get_node_subscriptions(NodeID) ->
|
||||
node_hometree:get_node_subscriptions(NodeID).
|
||||
|
||||
get_subscriptions(NodeID, Owner) ->
|
||||
node_hometree:get_subscriptions(NodeID, Owner).
|
||||
|
||||
set_subscriptions(NodeID, Owner, Subscriptions) ->
|
||||
node_hometree:set_subscriptions(NodeID, Owner, Subscriptions).
|
||||
|
||||
get_states(NodeID) ->
|
||||
node_hometree:get_states(NodeID).
|
||||
|
||||
get_state(NodeID, JID) ->
|
||||
node_hometree:get_state(NodeID, JID).
|
||||
|
||||
set_state(State) ->
|
||||
node_hometree:set_state(State).
|
||||
|
||||
get_items(NodeID, From) ->
|
||||
node_hometree:get_items(NodeID, From).
|
||||
|
||||
get_items(NodeID, JID, AccessModel, PresenceSubscription,
|
||||
RosterGroup, SubID) ->
|
||||
node_hometree:get_items(NodeID, JID, AccessModel, PresenceSubscription,
|
||||
RosterGroup, SubID).
|
||||
|
||||
get_item(NodeID, ItemID) ->
|
||||
node_hometree:get_item(NodeID, ItemID).
|
||||
|
||||
get_item(NodeID, ItemID, JID, AccessModel, PresenceSubscription,
|
||||
RosterGroup, SubID) ->
|
||||
node_hometree:get_item(NodeID, ItemID, JID, AccessModel,
|
||||
PresenceSubscription, RosterGroup, SubID).
|
||||
|
||||
set_item(Item) ->
|
||||
node_hometree:set_item(Item).
|
||||
|
||||
get_item_name(Host, Node, ID) ->
|
||||
node_hometree:get_item_name(Host, Node, ID).
|
@ -76,8 +76,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, dispatch},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -129,7 +128,7 @@ publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
|
||||
node_hometree:publish_item(
|
||||
SubNode#pubsub_node.id, Publisher, Model,
|
||||
MaxItems, ItemId, Payload)
|
||||
end, nodetree_default:get_subnodes(NodeId, Publisher)).
|
||||
end, nodetree_tree:get_subnodes(NodeId, Publisher, Publisher)).
|
||||
|
||||
remove_extra_items(_NodeId, _MaxItems, ItemIds) ->
|
||||
{result, {ItemIds, []}}.
|
||||
|
@ -69,8 +69,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, flat},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
|
@ -132,8 +132,7 @@ terminate(_Host, _ServerHost) ->
|
||||
%% {send_last_published_item, never},
|
||||
%% {presence_based_delivery, false}]'''
|
||||
options() ->
|
||||
[{node_type, default},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
@ -597,7 +596,7 @@ get_entity_affiliations(Host, Owner) ->
|
||||
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_default
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
@ -654,7 +653,7 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
end,
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_default
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
@ -774,9 +773,10 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
|
||||
SubKey = jlib:jid_tolower(JID),
|
||||
GenKey = jlib:jid_remove_resource(SubKey),
|
||||
GenState = get_state(NodeId, GenKey),
|
||||
SubState = get_state(NodeId, SubKey),
|
||||
Affiliation = GenState#pubsub_state.affiliation,
|
||||
Subscription = GenState#pubsub_state.subscriptions,
|
||||
Whitelisted = can_fetch_item(Affiliation, Subscription),
|
||||
Subscriptions = SubState#pubsub_state.subscriptions,
|
||||
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
|
||||
if
|
||||
%%SubID == "", ?? ->
|
||||
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
|
||||
@ -796,7 +796,7 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
|
||||
(AccessModel == whitelist) and (not Whitelisted) ->
|
||||
%% Node has whitelist access model and entity lacks required affiliation
|
||||
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
|
||||
(AccessModel == authorize) -> % TODO: to be done
|
||||
(AccessModel == authorize) and (not Whitelisted) ->
|
||||
%% Node has authorize access model
|
||||
{error, ?ERR_FORBIDDEN};
|
||||
%%MustPay ->
|
||||
@ -887,8 +887,10 @@ can_fetch_item(owner, _) -> true;
|
||||
can_fetch_item(member, _) -> true;
|
||||
can_fetch_item(publisher, _) -> true;
|
||||
can_fetch_item(outcast, _) -> false;
|
||||
can_fetch_item(none, subscribed) -> true;
|
||||
can_fetch_item(none, none) -> false;
|
||||
can_fetch_item(none, Subscriptions) ->
|
||||
lists:any(fun ({subscribed, _SubID}) -> true;
|
||||
(_) -> false
|
||||
end, Subscriptions);
|
||||
can_fetch_item(_Affiliation, _Subscription) -> false.
|
||||
|
||||
%% Returns the first item where Pred() is true in List
|
||||
|
@ -81,8 +81,7 @@ terminate(Host, ServerHost) ->
|
||||
ok.
|
||||
|
||||
options() ->
|
||||
[{node_type, pep},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, false},
|
||||
|
@ -76,8 +76,7 @@ terminate(Host, ServerHost) ->
|
||||
ok.
|
||||
|
||||
options() ->
|
||||
[{node_type, pep},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, false},
|
||||
@ -174,7 +173,7 @@ get_entity_affiliations(_Host, Owner) ->
|
||||
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_default
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
@ -206,7 +205,7 @@ get_entity_subscriptions(_Host, Owner) ->
|
||||
end,
|
||||
NodeTree = case ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of
|
||||
[{nodetree, N}] -> N;
|
||||
_ -> nodetree_default
|
||||
_ -> nodetree_tree
|
||||
end,
|
||||
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
|
||||
case NodeTree:get_node(N) of
|
||||
|
@ -78,8 +78,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, private},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
|
@ -78,8 +78,7 @@ terminate(Host, ServerHost) ->
|
||||
node_hometree:terminate(Host, ServerHost).
|
||||
|
||||
options() ->
|
||||
[{node_type, public},
|
||||
{deliver_payloads, true},
|
||||
[{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
|
246
src/mod_pubsub/nodetree_dag.erl
Normal file
246
src/mod_pubsub/nodetree_dag.erl
Normal file
@ -0,0 +1,246 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%% @author Brian Cully <bjc@kublai.com>
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
-module(nodetree_dag).
|
||||
-author('bjc@kublai.com').
|
||||
|
||||
%% API
|
||||
-export([init/3,
|
||||
terminate/2,
|
||||
options/0,
|
||||
set_node/1,
|
||||
get_node/3,
|
||||
get_node/2,
|
||||
get_node/1,
|
||||
get_nodes/2,
|
||||
get_nodes/1,
|
||||
get_parentnodes/3,
|
||||
get_parentnodes_tree/3,
|
||||
get_subnodes/3,
|
||||
get_subnodes_tree/3,
|
||||
create_node/5,
|
||||
delete_node/2]).
|
||||
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-behaviour(gen_pubsub_nodetree).
|
||||
|
||||
-define(DEFAULT_NODETYPE, leaf).
|
||||
-define(DEFAULT_PARENTS, []).
|
||||
-define(DEFAULT_CHILDREN, []).
|
||||
|
||||
-compile(export_all).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
init(Host, ServerHost, Opts) ->
|
||||
nodetree_tree:init(Host, ServerHost, Opts),
|
||||
mnesia:transaction(fun create_node/5,
|
||||
[Host, [], "default", service_jid(ServerHost), []]).
|
||||
|
||||
terminate(Host, ServerHost) ->
|
||||
nodetree_tree:terminate(Host, ServerHost).
|
||||
|
||||
create_node(Key, NodeID, Type, Owner, Options) ->
|
||||
OwnerJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||
case find_node(Key, NodeID) of
|
||||
false ->
|
||||
ID = pubsub_index:new(node),
|
||||
N = #pubsub_node{nodeid = oid(Key, NodeID),
|
||||
id = ID,
|
||||
type = Type,
|
||||
owners = [OwnerJID],
|
||||
options = Options},
|
||||
case set_node(N) of
|
||||
ok -> {ok, ID};
|
||||
Other -> Other
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_CONFLICT}
|
||||
end.
|
||||
|
||||
set_node(#pubsub_node{nodeid = {Key, _},
|
||||
owners = Owners,
|
||||
options = Options} = Node) ->
|
||||
Parents = find_opt(collection, ?DEFAULT_PARENTS, Options),
|
||||
case validate_parentage(Key, Owners, Parents) of
|
||||
true ->
|
||||
%% Update parents whenever the config changes.
|
||||
mnesia:write(Node#pubsub_node{parents = Parents});
|
||||
Other ->
|
||||
Other
|
||||
end.
|
||||
|
||||
delete_node(Key, NodeID) ->
|
||||
case find_node(Key, NodeID) of
|
||||
false ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND};
|
||||
Node ->
|
||||
%% Find all of N's children, update their configs to
|
||||
%% remove N from the collection setting.
|
||||
lists:foreach(fun (#pubsub_node{options = Opts} = Child) ->
|
||||
NewOpts = remove_config_parent(NodeID, Opts),
|
||||
Parents = find_opt(collection, ?DEFAULT_PARENTS, NewOpts),
|
||||
ok = mnesia:write(pubsub_node,
|
||||
Child#pubsub_node{
|
||||
parents = Parents,
|
||||
options = NewOpts},
|
||||
write)
|
||||
end, get_subnodes(Key, NodeID)),
|
||||
|
||||
%% Remove and return the requested node.
|
||||
mnesia:delete_object(pubsub_node, Node, write),
|
||||
[Node]
|
||||
end.
|
||||
|
||||
options() ->
|
||||
nodetree_tree:options().
|
||||
|
||||
get_node(Host, NodeID, _From) ->
|
||||
get_node(Host, NodeID).
|
||||
|
||||
get_node(Host, NodeID) ->
|
||||
case find_node(Host, NodeID) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
Node -> Node
|
||||
end.
|
||||
|
||||
get_node(NodeId) ->
|
||||
nodetree_tree:get_node(NodeId).
|
||||
|
||||
get_nodes(Key, From) ->
|
||||
nodetree_tree:get_nodes(Key, From).
|
||||
|
||||
get_nodes(Key) ->
|
||||
nodetree_tree:get_nodes(Key).
|
||||
|
||||
get_parentnodes(Host, NodeID, _From) ->
|
||||
case find_node(Host, NodeID) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
#pubsub_node{parents = Parents} ->
|
||||
Q = qlc:q([N || #pubsub_node{nodeid = {NHost, NNode}} = N <- mnesia:table(pubsub_node),
|
||||
Parent <- Parents,
|
||||
Host == NHost,
|
||||
Parent == NNode]),
|
||||
qlc:e(Q)
|
||||
end.
|
||||
|
||||
get_parentnodes_tree(Host, NodeID, _From) ->
|
||||
Pred = fun (NID, #pubsub_node{nodeid = {_, NNodeID}}) ->
|
||||
NID == NNodeID
|
||||
end,
|
||||
Tr = fun (#pubsub_node{parents = Parents}) -> Parents end,
|
||||
traversal_helper(Pred, Tr, Host, [NodeID]).
|
||||
|
||||
get_subnodes(Host, NodeID, _From) ->
|
||||
get_subnodes(Host, NodeID).
|
||||
|
||||
get_subnodes(Host, NodeID) ->
|
||||
case find_node(Host, NodeID) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
_ ->
|
||||
Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _},
|
||||
parents = Parents} = Node <- mnesia:table(pubsub_node),
|
||||
Host == NHost,
|
||||
lists:member(NodeID, Parents)]),
|
||||
qlc:e(Q)
|
||||
end.
|
||||
|
||||
get_subnodes_tree(Host, NodeID, _From) ->
|
||||
Pred = fun (NID, #pubsub_node{parents = Parents}) ->
|
||||
lists:member(NID, Parents)
|
||||
end,
|
||||
Tr = fun (#pubsub_node{nodeid = {_, N}}) -> N end,
|
||||
traversal_helper(Pred, Tr, Host, [NodeID]).
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
oid(Key, Name) -> {Key, Name}.
|
||||
|
||||
%% Key = jlib:jid() | host()
|
||||
%% NodeID = string()
|
||||
find_node(Key, NodeID) ->
|
||||
case mnesia:read(pubsub_node, oid(Key, NodeID), read) of
|
||||
[] -> false;
|
||||
[Node] -> Node
|
||||
end.
|
||||
|
||||
%% Key = jlib:jid() | host()
|
||||
%% Default = term()
|
||||
%% Options = [{Key = atom(), Value = term()}]
|
||||
find_opt(Key, Default, Options) ->
|
||||
case lists:keysearch(Key, 1, Options) of
|
||||
{value, {Key, Val}} -> Val;
|
||||
_ -> Default
|
||||
end.
|
||||
|
||||
traversal_helper(Pred, Tr, Host, NodeIDs) ->
|
||||
traversal_helper(Pred, Tr, 0, Host, NodeIDs, []).
|
||||
|
||||
traversal_helper(_Pred, _Tr, _Depth, _Host, [], Acc) ->
|
||||
Acc;
|
||||
traversal_helper(Pred, Tr, Depth, Host, NodeIDs, Acc) ->
|
||||
Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _}} = Node <- mnesia:table(pubsub_node),
|
||||
NodeID <- NodeIDs,
|
||||
Host == NHost,
|
||||
Pred(NodeID, Node)]),
|
||||
Nodes = qlc:e(Q),
|
||||
IDs = lists:flatmap(Tr, Nodes),
|
||||
traversal_helper(Pred, Tr, Depth + 1, Host, IDs, [{Depth, Nodes} | Acc]).
|
||||
|
||||
remove_config_parent(NodeID, Options) ->
|
||||
remove_config_parent(NodeID, Options, []).
|
||||
|
||||
remove_config_parent(_NodeID, [], Acc) ->
|
||||
lists:reverse(Acc);
|
||||
remove_config_parent(NodeID, [{collection, Parents} | T], Acc) ->
|
||||
remove_config_parent(NodeID, T,
|
||||
[{collection, lists:delete(NodeID, Parents)} | Acc]);
|
||||
remove_config_parent(NodeID, [H | T], Acc) ->
|
||||
remove_config_parent(NodeID, T, [H | Acc]).
|
||||
|
||||
validate_parentage(_Key, _Owners, []) ->
|
||||
true;
|
||||
validate_parentage(Key, Owners, [[] | T]) ->
|
||||
validate_parentage(Key, Owners, T);
|
||||
validate_parentage(Key, Owners, [ParentID | T]) ->
|
||||
case find_node(Key, ParentID) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
#pubsub_node{owners = POwners, options = POptions} ->
|
||||
NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions),
|
||||
MutualOwners = [O || O <- Owners, PO <- POwners,
|
||||
O == PO],
|
||||
case {MutualOwners, NodeType} of
|
||||
{[], _} -> {error, ?ERR_NOT_ALLOWED};
|
||||
{_, collection} -> validate_parentage(Key, Owners, T)
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (Host) -> jid()
|
||||
%% Host = host()
|
||||
%% @doc <p>Generate pubsub service JID.</p>
|
||||
service_jid(Host) ->
|
||||
case Host of
|
||||
{U,S,_} -> {jid, U, S, "", U, S, ""};
|
||||
_ -> {jid, "", Host, "", "", Host, ""}
|
||||
end.
|
@ -36,6 +36,8 @@
|
||||
-module(nodetree_tree).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
@ -50,6 +52,8 @@
|
||||
get_node/1,
|
||||
get_nodes/2,
|
||||
get_nodes/1,
|
||||
get_parentnodes/3,
|
||||
get_parentnodes_tree/3,
|
||||
get_subnodes/3,
|
||||
get_subnodes_tree/3,
|
||||
create_node/5,
|
||||
@ -124,6 +128,32 @@ get_nodes(Host, _From) ->
|
||||
get_nodes(Host) ->
|
||||
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}).
|
||||
|
||||
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
%% Depth = integer()
|
||||
%% Record = pubsubNode()
|
||||
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
|
||||
get_parentnodes(_Host, _Node, _From) ->
|
||||
[].
|
||||
|
||||
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
%% Depth = integer()
|
||||
%% Record = pubsubNode()
|
||||
%% @doc <p>Default node tree does not handle parents, return a list
|
||||
%% containing just this node.</p>
|
||||
get_parentnodes_tree(Host, Node, From) ->
|
||||
case get_node(Host, Node, From) of
|
||||
N when is_record(N, pubsub_node) ->
|
||||
[{0, mnesia:read(pubsub_node, {Host, Node})}];
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
%% @spec (Host, Node, From) -> [pubsubNode()] | {error, Reason}
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
@ -131,7 +161,11 @@ get_nodes(Host) ->
|
||||
get_subnodes(Host, Node, _From) ->
|
||||
get_subnodes(Host, Node).
|
||||
get_subnodes(Host, Node) ->
|
||||
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, parent = Node, _ = '_'}).
|
||||
Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _},
|
||||
parents = Parents} = N <- mnesia:table(pubsub_node),
|
||||
Host == NHost,
|
||||
lists:member(Node, Parents)]),
|
||||
qlc:e(Q).
|
||||
|
||||
%% @spec (Host, Index) -> [pubsubNodeIdx()] | {error, Reason}
|
||||
%% Host = mod_pubsub:host()
|
||||
@ -180,7 +214,7 @@ create_node(Host, Node, Type, Owner, Options) ->
|
||||
NodeId = pubsub_index:new(node),
|
||||
mnesia:write(#pubsub_node{nodeid = {Host, Node},
|
||||
id = NodeId,
|
||||
parent = ParentNode,
|
||||
parents = [ParentNode],
|
||||
type = Type,
|
||||
owners = [BJID],
|
||||
options = Options}),
|
||||
|
@ -47,6 +47,8 @@
|
||||
get_node/1,
|
||||
get_nodes/2,
|
||||
get_nodes/1,
|
||||
get_parentnodes/3,
|
||||
get_parentnodes_tree/3,
|
||||
get_subnodes/3,
|
||||
get_subnodes_tree/3,
|
||||
create_node/5,
|
||||
@ -104,6 +106,22 @@ get_nodes(Host, _From) ->
|
||||
get_nodes(_Host) ->
|
||||
[].
|
||||
|
||||
%% @spec (Host, Node, From) -> [pubsubNode()]
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
|
||||
get_parentnodes(_Host, _Node, _From) ->
|
||||
[].
|
||||
|
||||
%% @spec (Host, Node, From) -> [pubsubNode()]
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% From = mod_pubsub:jid()
|
||||
%% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p>
|
||||
get_parentnodes_tree(_Host, _Node, _From) ->
|
||||
[].
|
||||
|
||||
%% @spec (Host, Node, From) -> [pubsubNode()]
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
|
@ -91,7 +91,7 @@
|
||||
%%% <p>The <tt>parentid</tt> and <tt>type</tt> fields are indexed.</p>
|
||||
-record(pubsub_node, {nodeid,
|
||||
id,
|
||||
parent,
|
||||
parents = [],
|
||||
type = "flat",
|
||||
owners = [],
|
||||
options = []
|
||||
|
Loading…
Reference in New Issue
Block a user