diff --git a/src/jlib.hrl b/src/jlib.hrl index 62cd452a3..fda3e2ae1 100644 --- a/src/jlib.hrl +++ b/src/jlib.hrl @@ -49,6 +49,7 @@ -define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info"). -define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors"). -define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config"). +-define(NS_PUBSUB_SUB_OPTIONS, "http://jabber.org/protocol/pubsub#subscribe_options"). -define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization"). -define(NS_COMMANDS, "http://jabber.org/protocol/commands"). -define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams"). diff --git a/src/mod_pubsub/gen_pubsub_node.erl b/src/mod_pubsub/gen_pubsub_node.erl index 78256cd56..ba0d7386a 100644 --- a/src/mod_pubsub/gen_pubsub_node.erl +++ b/src/mod_pubsub/gen_pubsub_node.erl @@ -46,7 +46,7 @@ behaviour_info(callbacks) -> {create_node, 2}, {delete_node, 1}, {purge_node, 2}, - {subscribe_node, 7}, + {subscribe_node, 8}, {unsubscribe_node, 4}, {publish_item, 6}, {delete_item, 4}, @@ -57,8 +57,8 @@ behaviour_info(callbacks) -> {set_affiliation, 3}, {get_node_subscriptions, 1}, {get_entity_subscriptions, 2}, - {get_subscription, 2}, - {set_subscription, 3}, + {get_subscriptions, 2}, + {set_subscriptions, 3}, {get_states, 1}, {get_state, 2}, {set_state, 1}, diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index 1c5f688b1..af3f76f62 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -72,7 +72,7 @@ %% exports for console debug manual use -export([create_node/5, delete_node/3, - subscribe_node/4, + subscribe_node/5, unsubscribe_node/5, publish_item/6, delete_item/4, @@ -203,7 +203,8 @@ init([ServerHost, Opts]) -> ok end, ejabberd_router:register_route(Host), - update_database(Host, ServerHost), + update_node_database(Host, ServerHost), + update_state_database(Host, ServerHost), init_nodes(Host, ServerHost), State = #state{host = Host, server_host = ServerHost, @@ -258,12 +259,12 @@ init_nodes(Host, ServerHost) -> create_node(Host, ServerHost, ["home", ServerHost], service_jid(Host), ?STDNODE), ok. -update_database(Host, ServerHost) -> +update_node_database(Host, ServerHost) -> mnesia:del_table_index(pubsub_node, type), mnesia:del_table_index(pubsub_node, parentid), case catch mnesia:table_info(pubsub_node, attributes) of [host_node, host_parent, info] -> - ?INFO_MSG("upgrade pubsub tables",[]), + ?INFO_MSG("upgrade node pubsub tables",[]), F = fun() -> lists:foldl( fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, {RecList, NodeIdx}) -> @@ -290,11 +291,11 @@ update_database(Host, ServerHost) -> _ -> IAcc end end, [], ItemsList), - mnesia:write( - #pubsub_state{stateid = {JID, NodeIdx}, - items = UsrItems, - affiliation = Aff, - subscription = Sub}), + mnesia:write({pubsub_state, + {JID, NodeIdx}, + UsrItems, + Aff, + Sub}), case Aff of owner -> [JID | Acc]; _ -> Acc @@ -322,9 +323,11 @@ update_database(Host, ServerHost) -> end, case mnesia:transaction(FNew) of {atomic, Result} -> - ?INFO_MSG("Pubsub tables updated correctly: ~p", [Result]); + ?INFO_MSG("Pubsub node tables updated correctly: ~p", + [Result]); {aborted, Reason} -> - ?ERROR_MSG("Problem updating Pubsub tables:~n~p", [Reason]) + ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", + [Reason]) end; [nodeid, parentid, type, owners, options] -> F = fun({pubsub_node, NodeId, {_, Parent}, Type, Owners, Options}) -> @@ -362,9 +365,50 @@ update_database(Host, ServerHost) -> end, case mnesia:transaction(FNew) of {atomic, Result} -> - ?INFO_MSG("Pubsub tables updated correctly: ~p", [Result]); + ?INFO_MSG("Pubsub node tables updated correctly: ~p", + [Result]); {aborted, Reason} -> - ?ERROR_MSG("Problem updating Pubsub tables:~n~p", [Reason]) + ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", + [Reason]) + end; + _ -> + ok + end. + +update_state_database(_Host, _ServerHost) -> + case catch mnesia:table_info(pubsub_state, attributes) of + [stateid, items, affiliation, subscription] -> + ?INFO_MSG("upgrade state pubsub tables", []), + F = fun ({pubsub_state, {JID, NodeID}, Items, Aff, Sub}, Acc) -> + Subs = case Sub of + none -> + []; + _ -> + {result, SubID} = pubsub_subscription:subscribe_node(JID, NodeID, []), + [{Sub, SubID}] + end, + NewState = #pubsub_state{stateid = {JID, NodeID}, + items = Items, + affiliation = Aff, + subscriptions = Subs}, + [NewState | Acc] + end, + {atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3, + [F, [], pubsub_state]), + {atomic, ok} = mnesia:delete_table(pubsub_state), + {atomic, ok} = mnesia:create_table(pubsub_state, + [{disc_copies, [node()]}, + {attributes, record_info(fields, pubsub_state)}]), + FNew = fun () -> + lists:foreach(fun mnesia:write/1, NewRecs) + end, + case mnesia:transaction(FNew) of + {atomic, Result} -> + ?INFO_MSG("Pubsub state tables updated correctly: ~p", + [Result]); + {aborted, Reason} -> + ?ERROR_MSG("Problem updating Pubsub state tables:~n~p", + [Reason]) end; _ -> ok @@ -1077,22 +1121,17 @@ iq_get_vcard(Lang) -> iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)). -iq_pubsub(Host, ServerHost, From, IQType, SubEl, _Lang, Access, Plugins) -> +iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> {xmlelement, _, _, SubEls} = SubEl, - WithoutCdata = xml:remove_cdata(SubEls), - Configuration = lists:filter(fun({xmlelement, Name, _, _}) -> - Name == "configure" - end, WithoutCdata), - Action = WithoutCdata -- Configuration, - case Action of - [{xmlelement, Name, Attrs, Els}] -> + case xml:remove_cdata(SubEls) of + [{xmlelement, Name, Attrs, Els} | Rest] -> Node = case Host of {_, _, _} -> xml:get_attr_s("node", Attrs); _ -> string_to_node(xml:get_attr_s("node", Attrs)) end, case {IQType, Name} of {set, "create"} -> - Config = case Configuration of + Config = case Rest of [{xmlelement, "configure", _, C}] -> C; _ -> [] end, @@ -1142,8 +1181,12 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, _Lang, Access, Plugins) -> "item-required")} end; {set, "subscribe"} -> + Config = case Rest of + [{xmlelement, "options", _, C}] -> C; + _ -> [] + end, JID = xml:get_attr_s("jid", Attrs), - subscribe_node(Host, Node, From, JID); + subscribe_node(Host, Node, From, JID, Config); {set, "unsubscribe"} -> JID = xml:get_attr_s("jid", Attrs), SubId = xml:get_attr_s("subid", Attrs), @@ -1166,14 +1209,18 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, _Lang, Access, Plugins) -> {get, "affiliations"} -> get_affiliations(Host, From, Plugins); {get, "options"} -> - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscription-options")}; + SubID = xml:get_attr_s("subid", Attrs), + JID = xml:get_attr_s("jid", Attrs), + get_options(Host, Node, JID, SubID, Lang); {set, "options"} -> - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscription-options")}; + SubID = xml:get_attr_s("subid", Attrs), + JID = xml:get_attr_s("jid", Attrs), + set_options(Host, Node, JID, SubID, Els); _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} end; - _ -> - ?INFO_MSG("Too many actions: ~p", [Action]), + Other -> + ?INFO_MSG("Too many actions: ~p", [Other]), {error, ?ERR_BAD_REQUEST} end. @@ -1281,16 +1328,18 @@ find_authorization_response(Packet) -> %% Host = mod_pubsub:host() %% JID = jlib:jid() %% SNode = string() -%% Subscription = atom() -%% Plugins = [Plugin::string()] +%% Subscription = atom() | {atom(), mod_pubsub:subid()} %% @doc Send a message to JID with the supplied Subscription send_authorization_approval(Host, JID, SNode, Subscription) -> + SubAttrs = case Subscription of + {S, SID} -> [{"subscription", subscription_to_string(S)}, + {"subid", SID}]; + S -> [{"subscription", subscription_to_string(S)}] + end, Stanza = event_stanza( [{xmlelement, "subscription", - [{"node", SNode}, - {"jid", jlib:jid_to_string(JID)}, - {"subscription", subscription_to_string(Subscription)}], - []}]), + [{"node", SNode}, {"jid", jlib:jid_to_string(JID)}] ++ SubAttrs, + []}]), ejabberd_router ! {route, service_jid(Host), JID, Stanza}. handle_authorization_response(Host, From, To, Packet, XFields) -> @@ -1311,19 +1360,14 @@ handle_authorization_response(Host, From, To, Packet, XFields) -> end, Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) -> IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), Owners), - {result, Subscription} = node_call(Type, get_subscription, [NodeId, Subscriber]), + {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]), if not IsApprover -> {error, ?ERR_FORBIDDEN}; - Subscription /= pending -> - {error, ?ERR_UNEXPECTED_REQUEST}; true -> - NewSubscription = case Allow of - true -> subscribed; - false -> none - end, - send_authorization_approval(Host, Subscriber, SNode, NewSubscription), - node_call(Type, set_subscription, [NodeId, Subscriber, NewSubscription]) + update_auth(Host, SNode, Type, NodeId, + Subscriber, Allow, + Subscriptions) end end, case transaction(Host, Node, Action, sync_dirty) of @@ -1345,6 +1389,44 @@ handle_authorization_response(Host, From, To, Packet, XFields) -> jlib:make_error_reply(Packet, ?ERR_NOT_ACCEPTABLE)) end. +update_auth(Host, Node, Type, NodeId, Subscriber, + Allow, Subscriptions) -> + Subscription = lists:filter(fun({pending, _}) -> true; + (_) -> false + end, Subscriptions), + case Subscription of + [{pending, SubID}] -> %% TODO does not work if several pending + NewSubscription = case Allow of + true -> + node_call(Type, set_subscriptions, + [NodeId, Subscriber, + replace_subscription({subscribed, SubID}, + Subscriptions)]), + {subscribed, SubID}; + false -> + node_call(Type, unsubscribe_node, + [NodeId, Subscriber, Subscriber, + SubID]), + none + end, + send_authorization_approval(Host, Subscriber, Node, + NewSubscription), + {result, ok}; + _ -> + {error, ?ERR_UNEXPECTED_REQUEST} + end. + +replace_subscription(NewSub, Subs) -> + lists:foldl(fun(S, A) -> replace_subscription_helper(NewSub, S, A) end, + [], Subs). + +replace_subscription_helper({none, SubID}, {_, SubID}, Acc) -> + Acc; +replace_subscription_helper({NewSub, SubID}, {_, SubID}, Acc) -> + [{NewSub, SubID} | Acc]; +replace_subscription_helper(_, OldSub, Acc) -> + [OldSub | Acc]. + -define(XFIELD(Type, Label, Var, Val), {xmlelement, "field", [{"type", Type}, {"label", translate:translate(Lang, Label)}, @@ -1578,15 +1660,17 @@ delete_node(Host, Node, Owner) -> %%
Check if a notification must be delivered or not.
-is_to_deliver(_, none, _) -> false; -is_to_deliver(_, pending, _) -> false; -is_to_deliver(_, _, false) -> true; -is_to_deliver({User, Server, _}, _, true) -> +%% NodeOptions = [{atom(), term()}] +%% SubOptions = [{atom(), term()}] +%% @docCheck 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). + +sub_to_deliver(_LJID, SubOptions) -> + lists:all(fun sub_option_can_deliver/1, SubOptions). + +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), + presence_can_deliver(LJID, PresenceDelivery). + +presence_can_deliver(_, false) -> true; +presence_can_deliver({User, Server, _}, true) -> case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of [] -> false; Ss -> @@ -2433,30 +2650,34 @@ event_stanza(Els) -> %%%%%% broadcast functions -broadcast_publish_item(Host, Node, NodeId, Type, Options, Removed, ItemId, _From, Payload) -> - %broadcast(Host, Node, NodeId, Options, none, true, "items", ItemEls) +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} -> - Content = case get_option(Options, deliver_payloads) of + SubOptions = get_options_for_subs(Host, Node, NodeId, Subs), + Content = case get_option(NodeOptions, deliver_payloads) of true -> Payload; false -> [] end, Stanza = event_stanza( [{xmlelement, "items", nodeAttr(Node), [{xmlelement, "item", itemAttr(ItemId), Content}]}]), - broadcast_stanza(Host, Node, NodeId, Type, Options, Subs, Stanza), + broadcast_stanza(Host, Node, NodeId, Type, + NodeOptions, SubOptions, Stanza), case Removed of [] -> ok; _ -> - case get_option(Options, notify_retract) of + case get_option(NodeOptions, notify_retract) of true -> RetractStanza = event_stanza( [{xmlelement, "items", nodeAttr(Node), [{xmlelement, "retract", itemAttr(RId), []} || RId <- Removed]}]), - broadcast_stanza(Host, Node, NodeId, Type, Options, Subs, RetractStanza); + broadcast_stanza(Host, Node, NodeId, Type, + NodeOptions, SubOptions, + RetractStanza); _ -> ok end @@ -2466,22 +2687,24 @@ broadcast_publish_item(Host, Node, NodeId, Type, Options, Removed, ItemId, _From {result, false} end. -broadcast_retract_items(Host, Node, NodeId, Type, Options, ItemIds) -> - broadcast_retract_items(Host, Node, NodeId, Type, Options, ItemIds, false). -broadcast_retract_items(_Host, _Node, _NodeId, _Type, _Options, [], _ForceNotify) -> +broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds) -> + broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, false). +broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _ForceNotify) -> {result, false}; -broadcast_retract_items(Host, Node, NodeId, Type, Options, ItemIds, ForceNotify) -> - %broadcast(Host, Node, NodeId, Options, notify_retract, ForceNotify, "retract", RetractEls) - case (get_option(Options, notify_retract) or ForceNotify) of +broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNotify) -> + %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), Stanza = event_stanza( [{xmlelement, "items", nodeAttr(Node), [{xmlelement, "retract", itemAttr(ItemId), []} || ItemId <- ItemIds]}]), - broadcast_stanza(Host, Node, NodeId, Type, Options, Subs, Stanza), + broadcast_stanza(Host, Node, NodeId, Type, + NodeOptions, SubOptions, Stanza), {result, true}; _ -> {result, false} @@ -2490,18 +2713,20 @@ broadcast_retract_items(Host, Node, NodeId, Type, Options, ItemIds, ForceNotify) {result, false} end. -broadcast_purge_node(Host, Node, NodeId, Type, Options) -> - %broadcast(Host, Node, NodeId, Options, notify_retract, false, "purge", []) - case get_option(Options, notify_retract) of +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), Stanza = event_stanza( [{xmlelement, "purge", nodeAttr(Node), []}]), - broadcast_stanza(Host, Node, NodeId, Type, Options, Subs, Stanza), + broadcast_stanza(Host, Node, NodeId, Type, + NodeOptions, SubOptions, Stanza), {result, true}; _ -> {result, false} @@ -2510,43 +2735,47 @@ broadcast_purge_node(Host, Node, NodeId, Type, Options) -> {result, false} end. -broadcast_removed_node(Host, Node, NodeId, Type, Options, Subs) -> - %broadcast(Host, Node, NodeId, Options, notify_delete, false, "delete", []) - case get_option(Options, notify_delete) of +broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, Subs) -> + %broadcast(Host, Node, NodeId, NodeOptions, notify_delete, false, "delete", []) + case get_option(NodeOptions, notify_delete) of true -> case Subs 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, Options, Subs, Stanza), + broadcast_stanza(Host, Node, NodeId, Type, + NodeOptions, SubOptions, Stanza), {result, true} end; _ -> {result, false} end. -broadcast_config_notification(Host, Node, NodeId, Type, Options, Lang) -> - %broadcast(Host, Node, NodeId, Options, notify_config, false, "items", ConfigEls) - case get_option(Options, notify_config) of +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} -> - Content = case get_option(Options, deliver_payloads) of + SubOptions = get_options_for_subs(Host, Node, NodeId, Subs), + Content = case get_option(NodeOptions, deliver_payloads) of true -> [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}], - get_configure_xfields(Type, Options, Lang, [])}]; + get_configure_xfields(Type, NodeOptions, Lang, [])}]; false -> [] end, Stanza = event_stanza( [{xmlelement, "items", nodeAttr(Node), [{xmlelement, "item", itemAttr("configuration"), Content}]}]), - broadcast_stanza(Host, Node, NodeId, Type, Options, Subs, Stanza), + broadcast_stanza(Host, Node, NodeId, Type, + NodeOptions, SubOptions, Stanza), {result, true}; _ -> {result, false} @@ -2556,15 +2785,15 @@ broadcast_config_notification(Host, Node, NodeId, Type, Options, Lang) -> end. % TODO: merge broadcast code that way -%broadcast(Host, Node, NodeId, Type, Options, Feature, Force, ElName, SubEls) -> -% case (get_option(Options, Feature) or Force) of +%broadcast(Host, Node, NodeId, Type, NodeOptions, Feature, Force, ElName, SubEls) -> +% case (get_option(NodeOptions, Feature) or Force) of % true -> % case node_action(Host, Type, get_node_subscriptions, [NodeId]) of % {result, []} -> % {result, false}; % {result, Subs} -> % Stanza = event_stanza([{xmlelement, ElName, [{"node", node_to_string(Node)}], SubEls}]), -% broadcast_stanza(Host, Node, Type, Options, Subs, Stanza), +% broadcast_stanza(Host, Node, Type, NodeOptions, SubOpts, Stanza), % {result, true}; % _ -> % {result, false} @@ -2573,29 +2802,26 @@ broadcast_config_notification(Host, Node, NodeId, Type, Options, Lang) -> % {result, false} % end -broadcast_stanza(Host, Node, _NodeId, _Type, Options, Subscriptions, Stanza) -> - %AccessModel = get_option(Options, access_model), - PresenceDelivery = get_option(Options, presence_based_delivery), - BroadcastAll = get_option(Options, broadcast_all_resources), %% XXX this is not standard, but usefull +broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, Subs, 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 - lists:foreach(fun({LJID, Subscription}) -> - case is_to_deliver(LJID, Subscription, PresenceDelivery) of - true -> - LJIDs = case BroadcastAll of - true -> - {U, S, _} = LJID, - [{U, S, R} || R <- user_resources(U, S)]; - false -> - [LJID] - end, - lists:foreach(fun(To) -> - ejabberd_router ! {route, From, jlib:make_jid(To), Stanza} - end, LJIDs); - false -> - ok - end - end, Subscriptions), + DeliverSubs = lists:filter(fun({LJID, _Node, SubOptions}) -> + is_to_deliver(LJID, NodeOptions, SubOptions) + end, Subs), + lists:foreach(fun({LJID, _Node, _SubOptions}) -> + LJIDs = case BroadcastAll of + true -> + {U, S, _} = LJID, + [{U, S, R} || R <- user_resources(U, S)]; + false -> + [LJID] + end, + lists:foreach(fun(To) -> + ejabberd_router ! {route, From, jlib:make_jid(To), Stanza} + end, LJIDs) + end, DeliverSubs), %% Handles implicit presence subscriptions case Host of {LUser, LServer, LResource} -> @@ -2853,6 +3079,14 @@ set_configure(Host, Node, From, Els, Lang) -> {error, ?ERR_BAD_REQUEST} end. +get_options_for_subs(_Host, Node, NodeID, Subs) -> + lists:foldl(fun({JID, subscribed, SubID}, Acc) -> + {result, #pubsub_subscription{options = Options}} = pubsub_subscription:get_subscription(JID, NodeID, SubID), + [{JID, Node, Options} | Acc]; + (_, Acc) -> + Acc + end, [], Subs). + add_opt(Key, Value, Opts) -> Opts1 = lists:keydelete(Key, 1, Opts), [{Key, Value} | Opts1]. @@ -2999,7 +3233,7 @@ select_type(ServerHost, Host, Node) -> features() -> [ - %TODO "access-authorize", % OPTIONAL + % see plugin "access-authorize", % OPTIONAL "access-open", % OPTIONAL this relates to access_model option in node_hometree "access-presence", % OPTIONAL this relates to access_model option in node_pep %TODO "access-roster", % OPTIONAL @@ -3024,7 +3258,7 @@ features() -> %TODO "meta-data", % RECOMMENDED % see plugin "modify-affiliations", % OPTIONAL %TODO "multi-collection", % OPTIONAL - %TODO "multi-subscribe", % OPTIONAL + % see plugin "multi-subscribe", % OPTIONAL % see plugin "outcast-affiliation", % RECOMMENDED % see plugin "persistent-items", % RECOMMENDED "presence-notifications", % OPTIONAL @@ -3038,8 +3272,9 @@ features() -> "retrieve-default" % RECOMMENDED % see plugin "retrieve-items", % RECOMMENDED % see plugin "retrieve-subscriptions", % RECOMMENDED + %TODO "shim", % OPTIONAL % see plugin "subscribe", % REQUIRED - %TODO "subscription-options", % OPTIONAL + % see plugin "subscription-options", % OPTIONAL % see plugin "subscription-notifications" % OPTIONAL ]. features(Type) -> diff --git a/src/mod_pubsub/node.template b/src/mod_pubsub/node.template index 4911b30cf..336c547a9 100644 --- a/src/mod_pubsub/node.template +++ b/src/mod_pubsub/node.template @@ -45,7 +45,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -56,8 +56,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -154,11 +154,11 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(NodeId) -> node_hometree:get_node_subscriptions(NodeId). -get_subscription(NodeId, Owner) -> - node_hometree:get_subscription(NodeId, Owner). +get_subscriptions(NodeId, Owner) -> + node_hometree:get_subscriptions(NodeId, Owner). -set_subscription(NodeId, Owner, Subscription) -> - node_hometree:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscriptions) -> + node_hometree:set_subscriptions(NodeId, Owner, Subscription). get_states(NodeId) -> node_hometree:get_states(NodeId). diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl index 5ba5ecd83..0094674e7 100644 --- a/src/mod_pubsub/node_buddy.erl +++ b/src/mod_pubsub/node_buddy.erl @@ -46,7 +46,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -57,8 +57,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -121,8 +121,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). @@ -157,11 +157,11 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(NodeId) -> node_hometree:get_node_subscriptions(NodeId). -get_subscription(NodeId, Owner) -> - node_hometree:get_subscription(NodeId, Owner). +get_subscriptions(NodeId, Owner) -> + node_hometree:get_subscriptions(NodeId, Owner). -set_subscription(NodeId, Owner, Subscription) -> - node_hometree:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscriptions) -> + node_hometree:set_subscriptions(NodeId, Owner, Subscriptions). get_states(NodeId) -> node_hometree:get_states(NodeId). diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl index 48cb3bb57..6bbece021 100644 --- a/src/mod_pubsub/node_club.erl +++ b/src/mod_pubsub/node_club.erl @@ -46,7 +46,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -57,8 +57,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -120,8 +120,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). @@ -156,11 +156,11 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(NodeId) -> node_hometree:get_node_subscriptions(NodeId). -get_subscription(NodeId, Owner) -> - node_hometree:get_subscription(NodeId, Owner). +get_subscriptions(NodeId, Owner) -> + node_hometree:get_subscriptions(NodeId, Owner). -set_subscription(NodeId, Owner, Subscription) -> - node_hometree:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscription) -> + node_hometree:set_subscriptions(NodeId, Owner, Subscription). get_states(NodeId) -> node_hometree:get_states(NodeId). diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl index 63e7543c7..5661b39e9 100644 --- a/src/mod_pubsub/node_dispatch.erl +++ b/src/mod_pubsub/node_dispatch.erl @@ -44,7 +44,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -55,8 +55,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -118,7 +118,7 @@ delete_node(Removed) -> node_hometree:delete_node(Removed). subscribe_node(_NodeId, _Sender, _Subscriber, _AccessModel, - _SendLast, _PresenceSubscription, _RosterGroup) -> + _SendLast, _PresenceSubscription, _RosterGroup, _Options) -> {error, ?ERR_FORBIDDEN}. unsubscribe_node(_NodeId, _Sender, _Subscriber, _SubID) -> @@ -160,11 +160,11 @@ get_node_subscriptions(NodeId) -> %% DO NOT REMOVE node_hometree:get_node_subscriptions(NodeId). -get_subscription(_NodeId, _Owner) -> +get_subscriptions(_NodeId, _Owner) -> {result, []}. -set_subscription(NodeId, Owner, Subscription) -> - node_hometree:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscription) -> + node_hometree:set_subscriptions(NodeId, Owner, Subscription). get_states(NodeId) -> node_hometree:get_states(NodeId). diff --git a/src/mod_pubsub/node_flat.erl b/src/mod_pubsub/node_flat.erl index 754855ced..c530cf934 100644 --- a/src/mod_pubsub/node_flat.erl +++ b/src/mod_pubsub/node_flat.erl @@ -37,7 +37,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -48,8 +48,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -107,8 +107,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). @@ -143,11 +143,11 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(NodeId) -> node_hometree:get_node_subscriptions(NodeId). -get_subscription(NodeId, Owner) -> - node_hometree:get_subscription(NodeId, Owner). +get_subscriptions(NodeId, Owner) -> + node_hometree:get_subscriptions(NodeId, Owner). -set_subscription(NodeId, Owner, Subscription) -> - node_hometree:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscription) -> + node_hometree:set_subscriptions(NodeId, Owner, Subscription). get_states(NodeId) -> node_hometree:get_states(NodeId). diff --git a/src/mod_pubsub/node_hometree.erl b/src/mod_pubsub/node_hometree.erl index 6a83ed849..77acfdd94 100644 --- a/src/mod_pubsub/node_hometree.erl +++ b/src/mod_pubsub/node_hometree.erl @@ -53,7 +53,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -64,8 +64,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -91,15 +91,10 @@ %% plugin. It can be used for example by the developer to create the specific %% module database schema if it does not exists yet. init(_Host, _ServerHost, _Opts) -> + ok = pubsub_subscription:init(), 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)}]), @@ -158,11 +153,13 @@ options() -> features() -> ["create-nodes", "auto-create", + "access-authorize", "delete-nodes", "delete-items", "instant-nodes", "manage-subscriptions", "modify-affiliations", + "multi-subscribe", "outcast-affiliation", "persistent-items", "publish", @@ -172,7 +169,8 @@ features() -> "retrieve-items", "retrieve-subscriptions", "subscribe", - "subscription-notifications" + "subscription-notifications", + "subscription-options" ]. %% @spec (Host, ServerHost, Node, ParentNode, Owner, Access) -> bool() @@ -227,8 +225,10 @@ create_node(NodeId, Owner) -> %% Removed = [mod_pubsub:pubsubNode()] %% @docpurge items of deleted nodes after effective deletion.
delete_node(Removed) -> - Tr = fun(#pubsub_state{stateid = {J, _}, subscription = S}) -> - {J, S} + Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Ss}) -> + lists:map(fun(S) -> + {J, S} + end, Ss) end, Reply = lists:map( fun(#pubsub_node{id = NodeId} = PubsubNode) -> @@ -238,7 +238,7 @@ delete_node(Removed) -> del_items(NodeId, Items), del_state(NodeId, LJID) end, States), - {PubsubNode, lists:map(Tr, States)} + {PubsubNode, lists:flatmap(Tr, States)} end, Removed), {result, {default, broadcast, Reply}}. @@ -276,7 +276,7 @@ delete_node(Removed) -> %% %%In the default plugin module, the record is unchanged.
subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup) -> + SendLast, PresenceSubscription, RosterGroup, Options) -> SubKey = jlib:jid_tolower(Subscriber), GenKey = jlib:jid_remove_resource(SubKey), Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey), @@ -286,8 +286,11 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel, _ -> get_state(NodeId, SubKey) end, Affiliation = GenState#pubsub_state.affiliation, - Subscription = SubState#pubsub_state.subscription, + Subscriptions = SubState#pubsub_state.subscriptions, Whitelisted = lists:member(Affiliation, [member, publisher, owner]), + PendingSubscription = lists:any(fun({pending, _}) -> true; + (_) -> false + end, Subscriptions), if not Authorized -> %% JIDs do not match @@ -295,7 +298,7 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel, Affiliation == outcast -> %% Requesting entity is blocked {error, ?ERR_FORBIDDEN}; - Subscription == pending -> + PendingSubscription -> %% Requesting entity has pending subscription {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")}; (AccessModel == presence) and (not PresenceSubscription) -> @@ -307,9 +310,8 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel, (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 - %% Node has authorize access model - {error, ?ERR_FORBIDDEN}; + PendingSubscription -> + {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")}; %%MustPay -> %% % Payment is required for a subscription %% {error, ?ERR_PAYMENT_REQUIRED}; @@ -317,24 +319,24 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel, %% % Requesting entity is anonymous %% {error, ?ERR_FORBIDDEN}; true -> - NewSubscription = - if - AccessModel == authorize -> - pending; - %%NeedConfiguration -> - %% unconfigured - true -> - subscribed - end, - set_state(SubState#pubsub_state{subscription = NewSubscription}), - case NewSubscription of - subscribed -> - case SendLast of - never -> {result, {default, NewSubscription}}; - _ -> {result, {default, NewSubscription, send_last}} + case pubsub_subscription:subscribe_node(Subscriber, + NodeId, Options) of + {result, SubID} -> + NewSub = case AccessModel of + authorize -> pending; + _ -> subscribed + end, + set_state(SubState#pubsub_state{subscriptions = [{NewSub, SubID} | Subscriptions]}), + case {NewSub, SendLast} of + {subscribed, never} -> + {result, {default, subscribed, SubID}}; + {subscribed, _} -> + {result, {default, subscribed, SubID, send_last}}; + {_, _} -> + {result, {default, pending, SubID}} end; _ -> - {result, {default, NewSubscription}} + {error, ?ERR_INTERNAL_SERVER_ERROR} end end. @@ -343,10 +345,10 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel, %% NodeId = mod_pubsub:pubsubNodeId() %% Sender = mod_pubsub:jid() %% Subscriber = mod_pubsub:jid() -%% SubID = string() +%% SubID = mod_pubsub:subid() %% Reason = mod_pubsub:stanzaError() %% @docUnsubscribe the Subscriber from the Node.
-unsubscribe_node(NodeId, Sender, Subscriber, _SubId) -> +unsubscribe_node(NodeId, Sender, Subscriber, SubId) -> SubKey = jlib:jid_tolower(Subscriber), GenKey = jlib:jid_remove_resource(SubKey), Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey), @@ -355,6 +357,14 @@ unsubscribe_node(NodeId, Sender, Subscriber, _SubId) -> GenKey -> GenState; _ -> get_state(NodeId, SubKey) end, + Subscriptions = lists:filter(fun({_Sub, _SubID}) -> true; + (_SubID) -> false + end, SubState#pubsub_state.subscriptions), + SubIdExists = case SubId of + [] -> false; + List when is_list(List) -> true; + _ -> false + end, if %% Requesting entity is prohibited from unsubscribing entity not Authorized -> @@ -366,17 +376,49 @@ unsubscribe_node(NodeId, Sender, Subscriber, _SubId) -> %%InvalidSubID -> %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; %% Requesting entity is not a subscriber - SubState#pubsub_state.subscription == none -> + Subscriptions == [] -> {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")}; - %% Was just subscriber, remove the record - SubState#pubsub_state.affiliation == none -> - del_state(NodeId, SubKey), + %% Subid supplied, so use that. + SubIdExists -> + Sub = first_in_list(fun(S) -> + case S of + {_Sub, SubId} -> true; + _ -> false + end + end, SubState#pubsub_state.subscriptions), + case Sub of + {value, S} -> + delete_subscription(SubKey, NodeId, S, SubState), + {result, default}; + false -> + {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, + "not-subscribed")} + end; + %% No subid supplied, but there's only one matching + %% subscription, so use that. + length(Subscriptions) == 1 -> + delete_subscription(SubKey, NodeId, hd(Subscriptions), SubState), {result, default}; + %% No subid and more than one possible subscription match. true -> - set_state(SubState#pubsub_state{subscription = none}), - {result, default} + {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")} end. +delete_subscription(SubKey, NodeID, {Subscription, SubID}, SubState) -> + Affiliation = SubState#pubsub_state.affiliation, + AllSubs = SubState#pubsub_state.subscriptions, + NewSubs = AllSubs -- [{Subscription, SubID}], + pubsub_subscription:unsubscribe_node(SubKey, NodeID, SubID), + case {Affiliation, NewSubs} of + {none, []} -> + % Just a regular subscriber, and this is final item, so + % delete the state. + del_state(NodeID, SubKey); + _ -> + set_state(SubState#pubsub_state{subscriptions = NewSubs}) + end. + + %% @spec (NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) -> %% {true, PubsubItem} | {result, Reply} %% NodeId = mod_pubsub:pubsubNodeId() @@ -424,7 +466,7 @@ publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) -> _ -> get_state(NodeId, SubKey) end, Affiliation = GenState#pubsub_state.affiliation, - Subscription = SubState#pubsub_state.subscription, + Subscription = SubState#pubsub_state.subscriptions, if not ((PublishModel == open) or ((PublishModel == publishers) @@ -582,7 +624,7 @@ set_affiliation(NodeId, Owner, Affiliation) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), GenState = get_state(NodeId, GenKey), - case {Affiliation, GenState#pubsub_state.subscription} of + case {Affiliation, GenState#pubsub_state.subscriptions} of {none, none} -> del_state(NodeId, GenKey); _ -> @@ -614,9 +656,16 @@ get_entity_subscriptions(Host, Owner) -> [{nodetree, N}] -> N; _ -> nodetree_default end, - Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscription = S}, Acc) -> + Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) -> case NodeTree:get_node(N) of - #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, S, J}|Acc]; + #pubsub_node{nodeid = {Host, _}} = Node -> + lists:foldl(fun({subscribed, SubID}, Acc2) -> + [{Node, subscribed, SubID, J} | Acc2]; + ({pending, _SubID}, Acc2) -> + [{Node, pending, J} | Acc2]; + (S, Acc2) -> + [{Node, S, J} | Acc2] + end, Acc, Ss); _ -> Acc end end, [], States), @@ -624,24 +673,42 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(NodeId) -> {result, States} = get_states(NodeId), - Tr = fun(#pubsub_state{stateid = {J, _}, subscription = S}) -> - {J, S} + Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) -> + %% TODO: get rid of cases to handle non-list subscriptions + case Subscriptions of + [_|_] -> + lists:map(fun({subscribed, SubID}) -> {J, subscribed, SubID}; + ({pending, _SubID}) -> {J, pending}; + (S) -> {J, S} + end, Subscriptions); + _ -> + [{J, none}] + end end, - {result, lists:map(Tr, States)}. + {result, lists:flatmap(Tr, States)}. -get_subscription(NodeId, Owner) -> +get_subscriptions(NodeId, Owner) -> SubKey = jlib:jid_tolower(Owner), SubState = get_state(NodeId, SubKey), - {result, SubState#pubsub_state.subscription}. + {result, SubState#pubsub_state.subscriptions}. -set_subscription(NodeId, Owner, Subscription) -> +set_subscriptions(NodeId, Owner, Subscriptions) -> SubKey = jlib:jid_tolower(Owner), SubState = get_state(NodeId, SubKey), - case {Subscription, SubState#pubsub_state.affiliation} of - {none, none} -> + + OSIDs = [SID || {_, SID} <- SubState#pubsub_state.subscriptions], + NSIDs = [SID || {_, SID} <- Subscriptions], + RSIDs = OSIDs -- NSIDs, + + lists:foreach(fun(SubID) -> + pubsub_subscription:unsubscribe_node(SubKey, NodeId, + SubID) + end, RSIDs), + case {Subscriptions, SubState#pubsub_state.affiliation} of + {[], none} -> del_state(NodeId, SubKey); _ -> - set_state(SubState#pubsub_state{subscription = Subscription}) + set_state(SubState#pubsub_state{subscriptions = Subscriptions}) end. %% @spec (NodeId) -> [States] | [] @@ -708,7 +775,7 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) - GenKey = jlib:jid_remove_resource(SubKey), GenState = get_state(NodeId, GenKey), Affiliation = GenState#pubsub_state.affiliation, - Subscription = GenState#pubsub_state.subscription, + Subscription = GenState#pubsub_state.subscriptions, Whitelisted = can_fetch_item(Affiliation, Subscription), if %%SubID == "", ?? -> @@ -756,7 +823,7 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S GenKey = jlib:jid_remove_resource(SubKey), GenState = get_state(NodeId, GenKey), Affiliation = GenState#pubsub_state.affiliation, - Subscription = GenState#pubsub_state.subscription, + Subscription = GenState#pubsub_state.subscriptions, Whitelisted = can_fetch_item(Affiliation, Subscription), if %%SubID == "", ?? -> @@ -824,3 +891,11 @@ can_fetch_item(none, subscribed) -> true; can_fetch_item(none, none) -> false; can_fetch_item(_Affiliation, _Subscription) -> false. +%% Returns the first item where Pred() is true in List +first_in_list(_Pred, []) -> + false; +first_in_list(Pred, [H | T]) -> + case Pred(H) of + true -> {value, H}; + _ -> first_in_list(Pred, T) + end. diff --git a/src/mod_pubsub/node_mb.erl b/src/mod_pubsub/node_mb.erl index 4908481ed..af8e9222b 100644 --- a/src/mod_pubsub/node_mb.erl +++ b/src/mod_pubsub/node_mb.erl @@ -49,7 +49,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -60,8 +60,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -126,10 +126,10 @@ delete_node(Removed) -> node_pep:delete_node(Removed). subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup) -> + SendLast, PresenceSubscription, RosterGroup, Options) -> node_pep:subscribe_node( NodeId, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup). + PresenceSubscription, RosterGroup, Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> node_pep:unsubscribe_node(NodeId, Sender, Subscriber, SubID). @@ -164,11 +164,11 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(NodeId) -> node_pep:get_node_subscriptions(NodeId). -get_subscription(NodeId, Owner) -> - node_pep:get_subscription(NodeId, Owner). +get_subscriptions(NodeId, Owner) -> + node_pep:get_subscriptions(NodeId, Owner). -set_subscription(NodeId, Owner, Subscription) -> - node_pep:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscription) -> + node_pep:set_subscriptions(NodeId, Owner, Subscription). get_states(NodeId) -> node_pep:get_states(NodeId). diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl index fa28681b5..5ef3bff70 100644 --- a/src/mod_pubsub/node_pep.erl +++ b/src/mod_pubsub/node_pep.erl @@ -42,7 +42,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -53,8 +53,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -144,10 +144,10 @@ delete_node(Removed) -> end. subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup) -> + SendLast, PresenceSubscription, RosterGroup, Options) -> node_hometree:subscribe_node( NodeId, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup). + PresenceSubscription, RosterGroup, Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> case node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of @@ -208,9 +208,16 @@ get_entity_subscriptions(_Host, Owner) -> [{nodetree, N}] -> N; _ -> nodetree_default end, - Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscription = S}, Acc) -> + Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) -> case NodeTree:get_node(N) of - #pubsub_node{nodeid = {{_, D, _}, _}} = Node -> [{Node, S, J}|Acc]; + #pubsub_node{nodeid = {{_, D, _}, _}} = Node -> + lists:foldl(fun({subscribed, SubID}, Acc2) -> + [{Node, subscribed, SubID, J} | Acc2]; + ({pending, _SubID}, Acc2) -> + [{Node, pending, J} | Acc2]; + (S, Acc2) -> + [{Node, S, J} | Acc2] + end, Acc, Ss); _ -> Acc end end, [], States), @@ -224,11 +231,11 @@ get_node_subscriptions(NodeId) -> %% DO NOT REMOVE node_hometree:get_node_subscriptions(NodeId). -get_subscription(NodeId, Owner) -> - node_hometree:get_subscription(NodeId, Owner). +get_subscriptions(NodeId, Owner) -> + node_hometree:get_subscriptions(NodeId, Owner). -set_subscription(NodeId, Owner, Subscription) -> - node_hometree:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscription) -> + node_hometree:set_subscriptions(NodeId, Owner, Subscription). get_states(NodeId) -> node_hometree:get_states(NodeId). diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl index 6972c1a2e..d62365ba6 100644 --- a/src/mod_pubsub/node_private.erl +++ b/src/mod_pubsub/node_private.erl @@ -46,7 +46,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -57,8 +57,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -122,9 +122,10 @@ delete_node(Removed) -> node_hometree:delete_node(Removed). subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup) -> + PresenceSubscription, RosterGroup, Options) -> node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup). + SendLast, PresenceSubscription, RosterGroup, + Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID). @@ -159,11 +160,11 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(NodeId) -> node_hometree:get_node_subscriptions(NodeId). -get_subscription(NodeId, Owner) -> - node_hometree:get_subscription(NodeId, Owner). +get_subscriptions(NodeId, Owner) -> + node_hometree:get_subscriptions(NodeId, Owner). -set_subscription(NodeId, Owner, Subscription) -> - node_hometree:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscription) -> + node_hometree:set_subscriptions(NodeId, Owner, Subscription). get_states(NodeId) -> node_hometree:get_states(NodeId). diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl index 9dcb93d77..cc753c847 100644 --- a/src/mod_pubsub/node_public.erl +++ b/src/mod_pubsub/node_public.erl @@ -46,7 +46,7 @@ create_node/2, delete_node/1, purge_node/2, - subscribe_node/7, + subscribe_node/8, unsubscribe_node/4, publish_item/6, delete_item/4, @@ -57,8 +57,8 @@ set_affiliation/3, get_entity_subscriptions/2, get_node_subscriptions/1, - get_subscription/2, - set_subscription/3, + get_subscriptions/2, + set_subscriptions/3, get_states/1, get_state/2, set_state/1, @@ -120,8 +120,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). @@ -156,11 +156,11 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(NodeId) -> node_hometree:get_node_subscriptions(NodeId). -get_subscription(NodeId, Owner) -> - node_hometree:get_subscription(NodeId, Owner). +get_subscriptions(NodeId, Owner) -> + node_hometree:get_subscriptions(NodeId, Owner). -set_subscription(NodeId, Owner, Subscription) -> - node_hometree:set_subscription(NodeId, Owner, Subscription). +set_subscriptions(NodeId, Owner, Subscription) -> + node_hometree:set_subscriptions(NodeId, Owner, Subscription). get_states(NodeId) -> node_hometree:get_states(NodeId). diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl index b896d4614..ee129aa9f 100644 --- a/src/mod_pubsub/pubsub.hrl +++ b/src/mod_pubsub/pubsub.hrl @@ -101,13 +101,13 @@ %%% stateid = {ljid(), nodeidx()}}, %%% items = [ItemId::string()], %%% affiliation = affiliation(), -%%% subscription = subscription()}. +%%% subscriptions = [subscription()]}. %%%This is the format of the affiliations table. The type of the %%% table is: set,ram/disc.
-record(pubsub_state, {stateid, items = [], affiliation = none, - subscription = none + subscriptions = [] }). %%% @type pubsubItem() = #pubsub_item{ @@ -123,3 +123,12 @@ payload = [] }). + +%% @type pubsubSubscription() = #pubsub_subscription{ +%% subid = string(), +%% state_key = {ljid(), pubsubNodeId()}, +%% options = [{atom(), term()}] +%% }. +%%This is the format of the subscriptions table. The type of the +%% table is: set,ram/disc.
+-record(pubsub_subscription, {subid, options}). diff --git a/src/mod_pubsub/pubsub_subscription.erl b/src/mod_pubsub/pubsub_subscription.erl new file mode 100644 index 000000000..0ea20672e --- /dev/null +++ b/src/mod_pubsub/pubsub_subscription.erl @@ -0,0 +1,317 @@ +%%% ==================================================================== +%%% ``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. +%%% +%%% The Initial Developer of the Original Code is ProcessOne. +%%% Portions created by ProcessOne are Copyright 2006-2009, ProcessOne +%%% All Rights Reserved.'' +%%% This software is copyright 2006-2009, ProcessOne. +%%% +%%% @author Brian Cully