From fa5b06f542e928b884cf2a12e1abe6a82a6f80cb Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 29 May 2009 02:14:07 +0000 Subject: [PATCH] apply patch from EJAB-845 SVN Revision: 2118 --- src/jlib.hrl | 1 + src/mod_pubsub/gen_pubsub_node.erl | 6 +- src/mod_pubsub/mod_pubsub.erl | 487 ++++++++++++++++++------- src/mod_pubsub/node.template | 14 +- src/mod_pubsub/node_buddy.erl | 18 +- src/mod_pubsub/node_club.erl | 18 +- src/mod_pubsub/node_dispatch.erl | 14 +- src/mod_pubsub/node_flat.erl | 18 +- src/mod_pubsub/node_hometree.erl | 191 +++++++--- src/mod_pubsub/node_mb.erl | 18 +- src/mod_pubsub/node_pep.erl | 29 +- src/mod_pubsub/node_private.erl | 19 +- src/mod_pubsub/node_public.erl | 18 +- src/mod_pubsub/pubsub.hrl | 13 +- src/mod_pubsub/pubsub_subscription.erl | 317 ++++++++++++++++ 15 files changed, 913 insertions(+), 268 deletions(-) create mode 100644 src/mod_pubsub/pubsub_subscription.erl 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) -> %%
  • The node does not support subscriptions.
  • %%
  • The node does not exist.
  • %% -subscribe_node(Host, Node, From, JID) -> +subscribe_node(Host, Node, From, JID, Configuration) -> + {result, SubOpts} = pubsub_subscription:parse_options_xform(Configuration), Subscriber = case jlib:string_to_jid(JID) of error -> {"", "", ""}; J -> jlib:jid_tolower(J) end, - SubId = uniqid(), Action = fun(#pubsub_node{options = Options, owners = [Owner|_], type = Type, id = NodeId}) -> Features = features(Type), SubscribeFeature = lists:member("subscribe", Features), + OptionsFeature = lists:member("subscription-options", Features), + HasOptions = not (SubOpts == []), SubscribeConfig = get_option(Options, subscribe), AccessModel = get_option(Options, access_model), SendLast = get_option(Options, send_last_published_item), @@ -1613,43 +1697,50 @@ subscribe_node(Host, Node, From, JID) -> not SubscribeConfig -> %% Node does not support subscriptions {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")}; + HasOptions andalso not OptionsFeature -> + %% Node does not support subscription options + {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscription-options")}; true -> node_call(Type, subscribe_node, [NodeId, From, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup]) + PresenceSubscription, RosterGroup, + SubOpts]) end end, Reply = fun(Subscription) -> %% TODO, this is subscription-notification, should depends on node features + SubAttrs = case Subscription of + {subscribed, SubId} -> + [{"subscription", subscription_to_string(subscribed)}, + {"subid", SubId}]; + Other -> + [{"subscription", subscription_to_string(Other)}] + end, Fields = - [{"jid", jlib:jid_to_string(Subscriber)}, - {"subscription", subscription_to_string(Subscription)}|nodeAttr(Node)], + [{"jid", jlib:jid_to_string(Subscriber)} | SubAttrs], [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "subscription", - case Subscription of - subscribed -> [{"subid", SubId}|Fields]; - _ -> Fields - end, []}]}] + [{xmlelement, "subscription", Fields, []}]}] end, case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, subscribed, send_last}}} -> + {result, {TNode, {Result, subscribed, SubId, send_last}}} -> NodeId = TNode#pubsub_node.id, Type = TNode#pubsub_node.type, send_items(Host, Node, NodeId, Type, Subscriber, last), case Result of - default -> {result, Reply(subscribed)}; - _ -> {result, Result} - end; - {result, {TNode, {Result, Subscription}}} -> - case Subscription of - pending -> send_authorization_request(TNode, Subscriber); - _ -> ok - end, - case Result of - default -> {result, Reply(Subscription)}; + default -> {result, Reply({subscribed, SubId})}; _ -> {result, Result} end; + {result, {_TNode, {default, subscribed, SubId}}} -> + {result, Reply({subscribed, SubId})}; + {result, {_TNode, {Result, subscribed, _SubId}}} -> + {result, Result}; + {result, {TNode, {default, pending, _SubId}}} -> + send_authorization_request(TNode, Subscriber), + {result, Reply(pending)}; + {result, {TNode, {Result, pending}}} -> + send_authorization_request(TNode, Subscriber), + {result, Result}; {result, {_, Result}} -> %% this case should never occure anyway {result, Result}; @@ -2168,6 +2259,102 @@ set_affiliations(Host, Node, From, EntitiesEls) -> end end. +get_options(Host, Node, JID, SubID, Lang) -> + Action = fun(#pubsub_node{type = Type, id = NodeID}) -> + case lists:member("subscription-options", features(Type)) of + true -> + get_options_helper(JID, Lang, NodeID, SubID, Type); + false -> + {error, extended_error( + ?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, "subscription-options")} + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_Node, XForm}} -> {result, [XForm]}; + Error -> Error + end. + +get_options_helper(JID, Lang, NodeID, SubID, Type) -> + Subscriber = case jlib:string_to_jid(JID) of + error -> {"", "", ""}; + J -> jlib:jid_tolower(J) + end, + {result, Subs} = node_call(Type, get_subscriptions, + [NodeID, Subscriber]), + SubIDs = lists:foldl(fun({subscribed, SID}, Acc) -> + [SID | Acc]; + (_, Acc) -> + Acc + end, [], Subs), + case {SubID, SubIDs} of + {_, []} -> + {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "not-subscribed")}; + {[], [SID]} -> + read_sub(Subscriber, NodeID, SID, Lang); + {[], _} -> + {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "subid-required")}; + {_, _} -> + read_sub(Subscriber, NodeID, SubID, Lang) + end. + +read_sub(Subscriber, NodeID, SubID, Lang) -> + case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of + {error, notfound} -> + {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + {result, #pubsub_subscription{options = Options}} -> + pubsub_subscription:get_options_xform(Lang, Options) + end. + +set_options(Host, Node, JID, SubID, Configuration) -> + Action = fun(#pubsub_node{type = Type, id = NodeID}) -> + case lists:member("subscription-options", features(Type)) of + true -> + set_options_helper(Configuration, JID, NodeID, + SubID, Type); + false -> + {error, extended_error( + ?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, "subscription-options")} + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_Node, Result}} -> {result, Result}; + Error -> Error + end. + +set_options_helper(Configuration, JID, NodeID, SubID, Type) -> + Subscriber = case jlib:string_to_jid(JID) of + error -> {"", "", ""}; + J -> jlib:jid_tolower(J) + end, + {result, SubOpts} = pubsub_subscription:parse_options_xform(Configuration), + {result, Subs} = node_call(Type, get_subscriptions, + [NodeID, Subscriber]), + SubIDs = lists:foldl(fun({subscribed, SID}, Acc) -> + [SID | Acc]; + (_, Acc) -> + Acc + end, [], Subs), + case {SubID, SubIDs} of + {_, []} -> + {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "not-subscribed")}; + {[], [SID]} -> + write_sub(Subscriber, NodeID, SID, SubOpts); + {[], _} -> + {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "subid-required")}; + {_, _} -> + write_sub(Subscriber, NodeID, SubID, SubOpts) + end. + +write_sub(Subscriber, NodeID, SubID, Options) -> + case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, + Options) of + {error, notfound} -> + {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + {result, _} -> + {result, []} + end. %% @spec (Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} %% Host = host() @@ -2212,6 +2399,23 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> end; ({_, none, _}) -> []; + ({#pubsub_node{nodeid = {_, SubsNode}}, subscribed, SubID, SubJID}) -> + case Node of + [] -> + [{xmlelement, "subscription", + [{"jid", jlib:jid_to_string(SubJID)}, + {"subid", SubID}, + {"subscription", subscription_to_string(subscribed)}|nodeAttr(SubsNode)], + []}]; + SubsNode -> + [{xmlelement, "subscription", + [{"jid", jlib:jid_to_string(SubJID)}, + {"subid", SubID}, + {"subscription", subscription_to_string(subscribed)}], + []}]; + _ -> + [] + end; ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription, SubJID}) -> case Node of [] -> @@ -2308,7 +2512,7 @@ set_subscriptions(Host, Node, From, EntitiesEls) -> case lists:member(Owner, Owners) of true -> lists:foreach(fun({JID, Subscription}) -> - node_call(Type, set_subscription, [NodeId, JID, Subscription]) + node_call(Type, set_subscriptions, [NodeId, JID, Subscription]) end, Entities), {result, []}; _ -> @@ -2399,15 +2603,28 @@ service_jid(Host) -> _ -> {jid, "", Host, "", "", Host, ""} end. -%% @spec (LJID, Subscription, PresenceDelivery) -> boolean() +%% @spec (LJID, PresenceDelivery) -> boolean() %% LJID = jid() -%% Subscription = atom() -%% PresenceDelivery = boolean() -%% @doc

    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()}] +%% @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). + +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()] %% @doc

    purge 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() %% @doc

    Unsubscribe 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 +%%% @version {@vsn}, {@date} {@time} +%%% @end +%%% ==================================================================== + +-module(pubsub_subscription). +-author("bjc@kublai.com"). + +%% API +-export([init/0, + subscribe_node/3, + unsubscribe_node/3, + get_subscription/3, + set_subscription/4, + get_options_xform/2, + parse_options_xform/1]). + +-include_lib("stdlib/include/qlc.hrl"). + +-include("pubsub.hrl"). +-include("jlib.hrl"). + +-define(PUBSUB_DELIVER, "pubsub#deliver"). +-define(PUBSUB_DIGEST, "pubsub#digest"). +-define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency"). +-define(PUBSUB_EXPIRE, "pubsub#expire"). +-define(PUBSUB_INCLUDE_BODY, "pubsub#include_body"). +-define(PUBSUB_SHOW_VALUES, "pubsub#show-values"). +-define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type"). +-define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth"). + +-define(DELIVER_LABEL, + "Whether an entity wants to receive or disable notifications"). +-define(DIGEST_LABEL, + "Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually"). +-define(DIGEST_FREQUENCY_LABEL, + "The minimum number of milliseconds between sending any two notification digests"). +-define(EXPIRE_LABEL, + "The DateTime at which a leased subscription will end or has ended"). +-define(INCLUDE_BODY_LABEL, + "Whether an entity wants to receive an XMPP message body in addition to the payload format"). +-define(SHOW_VALUES_LABEL, + "The presence states for which an entity wants to receive notifications"). +-define(SUBSCRIPTION_TYPE_LABEL, + "Type of notification to receive"). +-define(SUBSCRIPTION_DEPTH_LABEL, + "Depth from subscription for which to receive notifications"). + +-define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away"). +-define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat"). +-define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)"). +-define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)"). +-define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)"). + +-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL, + "Receive notification of new items only"). +-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL, + "Receive notification of new nodes only"). + +-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, + "Receive notification from direct child nodes only"). +-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, + "Receive notification from all descendent nodes"). + +%%==================================================================== +%% API +%%==================================================================== +init() -> + ok = create_table(). + +subscribe_node(JID, NodeID, Options) -> + case mnesia:transaction(fun add_subscription/3, + [JID, NodeID, Options]) of + {atomic, Result} -> {result, Result}; + {aborted, Error} -> Error + end. + +unsubscribe_node(JID, NodeID, SubID) -> + case mnesia:transaction(fun delete_subscription/3, + [JID, NodeID, SubID]) of + {atomic, Result} -> {result, Result}; + {aborted, Error} -> Error + end. + +get_subscription(JID, NodeID, SubID) -> + case mnesia:transaction(fun read_subscription/3, + [JID, NodeID, SubID]) of + {atomic, Result} -> {result, Result}; + {aborted, Error} -> Error + end. + +set_subscription(JID, NodeID, SubID, Options) -> + case mnesia:transaction(fun write_subscription/4, + [JID, NodeID, SubID, Options]) of + {atomic, Result} -> {result, Result}; + {aborted, Error} -> Error + end. + +get_options_xform(Lang, Options) -> + Keys = [deliver, show_values, subscription_type, subscription_depth], + XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys], + + {result, {xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}], + [{xmlelement, "value", [], + [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] ++ XFields}}. + +parse_options_xform(XFields) -> + case xml:remove_cdata(XFields) of + [] -> {result, []}; + [{xmlelement, "x", _Attrs, _Els} = XEl] -> + case jlib:parse_xdata_submit(XEl) of + XData when is_list(XData) -> + case set_xoption(XData, []) of + Opts when is_list(Opts) -> {result, Opts}; + Other -> Other + end; + Other -> + Other + end; + Other -> + Other + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== +create_table() -> + case mnesia:create_table(pubsub_subscription, + [{disc_copies, [node()]}, + {attributes, record_info(fields, pubsub_subscription)}, + {type, set}]) of + {atomic, ok} -> ok; + {aborted, {already_exists, _}} -> ok; + Other -> Other + end. + +add_subscription(_JID, _NodeID, Options) -> + SubID = make_subid(), + Record = #pubsub_subscription{subid = SubID, options = Options}, + mnesia:write(Record), + SubID. + +delete_subscription(JID, NodeID, SubID) -> + Sub = read_subscription(JID, NodeID, SubID), + mnesia:delete({pubsub_subscription, SubID}), + Sub. + +read_subscription(_JID, _NodeID, SubID) -> + Q = qlc:q([Sub || Sub <- mnesia:table(pubsub_subscription), + Sub#pubsub_subscription.subid == SubID]), + case qlc:e(Q) of + [Sub] -> Sub; + [] -> mnesia:abort({error, notfound}) + end. + +write_subscription(JID, NodeID, SubID, Options) -> + Sub = read_subscription(JID, NodeID, SubID), + mnesia:write(Sub#pubsub_subscription{options = Options}). + +make_subid() -> + {T1, T2, T3} = now(), + lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). + +%% +%% Subscription XForm processing. +%% + +%% Return processed options, with types converted and so forth, using +%% Opts as defaults. +set_xoption([], Opts) -> + Opts; +set_xoption([{Var, Value} | T], Opts) -> + NewOpts = case var_xfield(Var) of + {error, _} -> + Opts; + Key -> + Val = val_xfield(Key, Value), + lists:keystore(Key, 1, Opts, {Key, Val}) + end, + set_xoption(T, NewOpts). + +%% Return the options list's key for an XForm var. +var_xfield(?PUBSUB_DELIVER) -> deliver; +var_xfield(?PUBSUB_DIGEST) -> digest; +var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency; +var_xfield(?PUBSUB_EXPIRE) -> expire; +var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; +var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; +var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type; +var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth; +var_xfield(_) -> {error, badarg}. + +%% Convert Values for option list's Key. +val_xfield(deliver, [Val]) -> xopt_to_bool(Val); +val_xfield(digest, [Val]) -> xopt_to_bool(Val); +val_xfield(digest_frequency, [Val]) -> list_to_integer(Val); +val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val); +val_xfield(include_body, [Val]) -> xopt_to_bool(Val); +val_xfield(show_values, Vals) -> Vals; +val_xfield(subscription_type, ["items"]) -> items; +val_xfield(subscription_type, ["nodes"]) -> nodes; +val_xfield(subscription_depth, ["all"]) -> all; +val_xfield(subscription_depth, [Depth]) -> + case catch list_to_integer(Depth) of + N when is_integer(N) -> N; + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end. + +%% Convert XForm booleans to Erlang booleans. +xopt_to_bool("0") -> false; +xopt_to_bool("1") -> true; +xopt_to_bool("false") -> false; +xopt_to_bool("true") -> true; +xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}. + +%% Return a field for an XForm for Key, with data filled in, if +%% applicable, from Options. +get_option_xfield(Lang, Key, Options) -> + Var = xfield_var(Key), + Label = xfield_label(Key), + {Type, OptEls} = type_and_options(xfield_type(Key), Lang), + Vals = case lists:keysearch(Key, 1, Options) of + {value, {_, Val}} -> + [tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)]; + false -> + [] + end, + {xmlelement, "field", + [{"var", Var}, {"type", Type}, + {"label", translate:translate(Lang, Label)}], + OptEls ++ Vals}. + +type_and_options({Type, Options}, Lang) -> + {Type, [tr_xfield_options(O, Lang) || O <- Options]}; +type_and_options(Type, _Lang) -> + {Type, []}. + +tr_xfield_options({Value, Label}, Lang) -> + {xmlelement, "option", + [{"label", translate:translate(Lang, Label)}], [{xmlelement, "value", [], + [{xmlcdata, Value}]}]}. + +tr_xfield_values(Value) -> + {xmlelement, "value", [], [{xmlcdata, Value}]}. + +%% Return the XForm variable name for a subscription option key. +xfield_var(deliver) -> ?PUBSUB_DELIVER; +xfield_var(digest) -> ?PUBSUB_DIGEST; +xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY; +xfield_var(expire) -> ?PUBSUB_EXPIRE; +xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; +xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; +xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE; +xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH. + +%% Return the XForm variable type for a subscription option key. +xfield_type(deliver) -> "boolean"; +xfield_type(digest) -> "boolean"; +xfield_type(digest_frequency) -> "text-single"; +xfield_type(expire) -> "text-single"; +xfield_type(include_body) -> "boolean"; +xfield_type(show_values) -> + {"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL}, + {"chat", ?SHOW_VALUE_CHAT_LABEL}, + {"dnd", ?SHOW_VALUE_DND_LABEL}, + {"online", ?SHOW_VALUE_ONLINE_LABEL}, + {"xa", ?SHOW_VALUE_XA_LABEL}]}; +xfield_type(subscription_type) -> + {"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, + {"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; +xfield_type(subscription_depth) -> + {"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, + {"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. + +%% Return the XForm variable label for a subscription option key. +xfield_label(deliver) -> ?DELIVER_LABEL; +xfield_label(digest) -> ?DIGEST_LABEL; +xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL; +xfield_label(expire) -> ?EXPIRE_LABEL; +xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; +xfield_label(show_values) -> ?SHOW_VALUES_LABEL; +xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL; +xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL. + +%% Return the XForm value for a subscription option key. +xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; +xfield_val(digest, Val) -> [bool_to_xopt(Val)]; +xfield_val(digest_frequency, Val) -> [integer_to_list(Val)]; +xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)]; +xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; +xfield_val(show_values, Val) -> Val; +xfield_val(subscription_type, items) -> ["items"]; +xfield_val(subscription_type, nodes) -> ["nodes"]; +xfield_val(subscription_depth, all) -> ["all"]; +xfield_val(subscription_depth, N) -> [integer_to_list(N)]. + +%% Convert erlang booleans to XForms. +bool_to_xopt(false) -> "false"; +bool_to_xopt(true) -> "true".