diff --git a/src/mod_pubsub/Makefile.win32 b/src/mod_pubsub/Makefile.win32 index f981329c9..d3cdcc499 100644 --- a/src/mod_pubsub/Makefile.win32 +++ b/src/mod_pubsub/Makefile.win32 @@ -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 diff --git a/src/mod_pubsub/gen_pubsub_nodetree.erl b/src/mod_pubsub/gen_pubsub_nodetree.erl index f012b3d0b..f55694500 100644 --- a/src/mod_pubsub/gen_pubsub_nodetree.erl +++ b/src/mod_pubsub/gen_pubsub_nodetree.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}, diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index af3f76f62..b36d1a3ff 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -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

Check if a notification must be delivered or not based on %% node and subscription options.

-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) ]. %%

There are several reasons why the node configuration request might fail:

@@ -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]]. diff --git a/src/mod_pubsub/node.template b/src/mod_pubsub/node.template index 336c547a9..534c18226 100644 --- a/src/mod_pubsub/node.template +++ b/src/mod_pubsub/node.template @@ -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). diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl index 0094674e7..2e8dd0249 100644 --- a/src/mod_pubsub/node_buddy.erl +++ b/src/mod_pubsub/node_buddy.erl @@ -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}, diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl index 6bbece021..91ff520c6 100644 --- a/src/mod_pubsub/node_club.erl +++ b/src/mod_pubsub/node_club.erl @@ -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}, diff --git a/src/mod_pubsub/node_dag.erl b/src/mod_pubsub/node_dag.erl new file mode 100644 index 000000000..954a26a95 --- /dev/null +++ b/src/mod_pubsub/node_dag.erl @@ -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 +%%% @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). diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl index 5661b39e9..f67ab81fa 100644 --- a/src/mod_pubsub/node_dispatch.erl +++ b/src/mod_pubsub/node_dispatch.erl @@ -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, []}}. diff --git a/src/mod_pubsub/node_flat.erl b/src/mod_pubsub/node_flat.erl index c530cf934..a4fbb3b67 100644 --- a/src/mod_pubsub/node_flat.erl +++ b/src/mod_pubsub/node_flat.erl @@ -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}, diff --git a/src/mod_pubsub/node_hometree.erl b/src/mod_pubsub/node_hometree.erl index 77acfdd94..45c695ae2 100644 --- a/src/mod_pubsub/node_hometree.erl +++ b/src/mod_pubsub/node_hometree.erl @@ -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 diff --git a/src/mod_pubsub/node_mb.erl b/src/mod_pubsub/node_mb.erl index af8e9222b..70ceabbb1 100644 --- a/src/mod_pubsub/node_mb.erl +++ b/src/mod_pubsub/node_mb.erl @@ -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}, diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl index 5ef3bff70..8e2e7e0a3 100644 --- a/src/mod_pubsub/node_pep.erl +++ b/src/mod_pubsub/node_pep.erl @@ -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 diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl index d62365ba6..e9c925403 100644 --- a/src/mod_pubsub/node_private.erl +++ b/src/mod_pubsub/node_private.erl @@ -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}, diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl index cc753c847..ae7beff84 100644 --- a/src/mod_pubsub/node_public.erl +++ b/src/mod_pubsub/node_public.erl @@ -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}, diff --git a/src/mod_pubsub/nodetree_dag.erl b/src/mod_pubsub/nodetree_dag.erl new file mode 100644 index 000000000..1a5921fb8 --- /dev/null +++ b/src/mod_pubsub/nodetree_dag.erl @@ -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 +%%% @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

Generate pubsub service JID.

+service_jid(Host) -> + case Host of + {U,S,_} -> {jid, U, S, "", U, S, ""}; + _ -> {jid, "", Host, "", "", Host, ""} + end. diff --git a/src/mod_pubsub/nodetree_tree.erl b/src/mod_pubsub/nodetree_tree.erl index 8f49a06b0..767b0e9c8 100644 --- a/src/mod_pubsub/nodetree_tree.erl +++ b/src/mod_pubsub/nodetree_tree.erl @@ -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

Default node tree does not handle parents, return empty list.

+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

Default node tree does not handle parents, return a list +%% containing just this node.

+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}), diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl index 4b331aeb9..e003f9058 100644 --- a/src/mod_pubsub/nodetree_virtual.erl +++ b/src/mod_pubsub/nodetree_virtual.erl @@ -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

Virtual node tree does not handle parent/child. Child list is empty.

+get_parentnodes(_Host, _Node, _From) -> + []. + +%% @spec (Host, Node, From) -> [pubsubNode()] +%% Host = mod_pubsub:host() +%% Node = mod_pubsub:pubsubNode() +%% From = mod_pubsub:jid() +%% @doc

Virtual node tree does not handle parent/child. Child list is empty.

+get_parentnodes_tree(_Host, _Node, _From) -> + []. + %% @spec (Host, Node, From) -> [pubsubNode()] %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl index ee129aa9f..f406a05c3 100644 --- a/src/mod_pubsub/pubsub.hrl +++ b/src/mod_pubsub/pubsub.hrl @@ -91,7 +91,7 @@ %%%

The parentid and type fields are indexed.

-record(pubsub_node, {nodeid, id, - parent, + parents = [], type = "flat", owners = [], options = []