diff options
Diffstat (limited to 'src/pubsub_subscription.erl')
-rw-r--r-- | src/pubsub_subscription.erl | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/src/pubsub_subscription.erl b/src/pubsub_subscription.erl new file mode 100644 index 000000000..bb09cdd60 --- /dev/null +++ b/src/pubsub_subscription.erl @@ -0,0 +1,463 @@ +%%% ==================================================================== +%%% ``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-2013, ProcessOne +%%% All Rights Reserved.'' +%%% This software is copyright 2006-2013, ProcessOne. +%%% +%%% @author Brian Cully <bjc@kublai.com> +%%% @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]). + +% Internal function also exported for use in transactional bloc from pubsub plugins +-export([add_subscription/3, delete_subscription/3, + read_subscription/3, write_subscription/4]). + +-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 catch mnesia:sync_dirty(fun add_subscription/3, + [JID, NodeID, Options]) + of + {'EXIT', {aborted, Error}} -> Error; + {error, Error} -> {error, Error}; + Result -> {result, Result} + end. + +unsubscribe_node(JID, NodeID, SubID) -> + case catch mnesia:sync_dirty(fun delete_subscription/3, + [JID, NodeID, SubID]) + of + {'EXIT', {aborted, Error}} -> Error; + {error, Error} -> {error, Error}; + Result -> {result, Result} + end. + +get_subscription(JID, NodeID, SubID) -> + case catch mnesia:sync_dirty(fun read_subscription/3, + [JID, NodeID, SubID]) + of + {'EXIT', {aborted, Error}} -> Error; + {error, Error} -> {error, Error}; + Result -> {result, Result} + end. + +set_subscription(JID, NodeID, SubID, Options) -> + case catch mnesia:sync_dirty(fun write_subscription/4, + [JID, NodeID, SubID, Options]) + of + {'EXIT', {aborted, Error}} -> Error; + {error, Error} -> {error, Error}; + Result -> {result, Result} + end. + + +get_options_xform(Lang, Options) -> + Keys = [deliver, show_values, subscription_type, + subscription_depth], + XFields = [get_option_xfield(Lang, Key, Options) + || Key <- Keys], + {result, + #xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [#xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] + ++ XFields}}. + +parse_options_xform(XFields) -> + case xml:remove_cdata(XFields) of + [#xmlel{name = <<"x">>} = XEl] -> + case jlib:parse_xdata_submit(XEl) of + XData when is_list(XData) -> + Opts = set_xoption(XData, []), + {result, Opts}; + Other -> Other + end; + _ -> {result, []} + 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. + +-spec(add_subscription/3 :: +( + _JID :: ljid(), + _NodeID :: mod_pubsub:nodeIdx(), + Options :: [] | mod_pubsub:subOptions()) + -> SubId :: mod_pubsub:subId() +). + +add_subscription(_JID, _NodeID, []) -> make_subid(); +add_subscription(_JID, _NodeID, Options) -> + SubID = make_subid(), + mnesia:write(#pubsub_subscription{subid = SubID, + options = Options}), + SubID. + +-spec(delete_subscription/3 :: +( + _JID :: _, + _NodeID :: _, + SubId :: mod_pubsub:subId()) + -> ok +). + +delete_subscription(_JID, _NodeID, SubID) -> + mnesia:delete({pubsub_subscription, SubID}). + +-spec(read_subscription/3 :: +( + _JID :: ljid(), + _NodeID :: _, + SubID :: mod_pubsub:subId()) + -> mod_pubsub:pubsubSubscription() + | {error, notfound} +). + +read_subscription(_JID, _NodeID, SubID) -> + case mnesia:read({pubsub_subscription, SubID}) of + [Sub] -> Sub; + _ -> {error, notfound} + end. + +-spec(write_subscription/4 :: +( + _JID :: ljid(), + _NodeID :: _, + SubID :: mod_pubsub:subId(), + Options :: mod_pubsub:subOptions()) + -> ok +). + +write_subscription(_JID, _NodeID, SubID, Options) -> + mnesia:write(#pubsub_subscription{subid = SubID, + options = Options}). + +-spec(make_subid/0 :: () -> SubId::mod_pubsub:subId()). +make_subid() -> + {T1, T2, T3} = now(), + iolist_to_binary(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. +%% Convert Values for option list's Key. +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}. + +val_xfield(deliver, [Val]) -> xopt_to_bool(Val); +%val_xfield(digest, [Val]) -> xopt_to_bool(Val); +%val_xfield(digest_frequency, [Val]) -> +% jlib:binary_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 jlib:binary_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}. + +-spec(get_option_xfield/3 :: +( + Lang :: binary(), + Key :: atom(), + Options :: mod_pubsub:subOptions()) + -> xmlel() +). + +%% 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, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, Var}, {<<"type">>, Type}, + {<<"label">>, translate:translate(Lang, Label)}], + children = 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) -> + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, translate:translate(Lang, Label)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}]}. + +tr_xfield_values(Value) -> +%% Return the XForm variable name for a subscription option key. +%% Return the XForm variable type for a subscription option key. + #xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}. + +-spec(xfield_var/1 :: +( + Var :: 'deliver' +% | 'digest' +% | 'digest_frequency' +% | 'expire' +% | 'include_body' + | 'show_values' + | 'subscription_type' + | 'subscription_depth') + -> binary() +). + +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. + +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; +%% Return the XForm value for a subscription option key. +%% Convert erlang booleans to XForms. +xfield_label(subscription_type) -> + ?SUBSCRIPTION_TYPE_LABEL; +xfield_label(subscription_depth) -> + ?SUBSCRIPTION_DEPTH_LABEL. + +-spec(xfield_val/2 :: +( + Field :: 'deliver' +% | 'digest' +% | 'digest_frequency' +% | 'expire' +% | 'include_body' + | 'show_values' + | 'subscription_type' + | 'subscription_depth', + Val :: boolean() + | binary() + | integer() + | [binary()]) +% | erlang:timestamp()) + -> [binary()] +). + +xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; +%xfield_val(digest, Val) -> [bool_to_xopt(Val)]; +%xfield_val(digest_frequency, Val) -> +% [iolist_to_binary(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) -> + [iolist_to_binary(integer_to_list(N))]. + + +bool_to_xopt(true) -> <<"true">>; +bool_to_xopt(false) -> <<"false">>. |