1
%%% ====================================================================
2
%%% ``The contents of this file are subject to the Erlang Public License,
3
%%% Version 1.1, (the "License"); you may not use this file except in
4
%%% compliance with the License. You should have received a copy of the
5
%%% Erlang Public License along with this software. If not, it can be
6
%%% retrieved via the world wide web at http://www.erlang.org/.
8
%%% Software distributed under the License is distributed on an "AS IS"
9
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
10
%%% the License for the specific language governing rights and limitations
11
%%% under the License.
13
%%% The Initial Developer of the Original Code is ProcessOne.
14
%%% Portions created by ProcessOne are Copyright 2006-2009, ProcessOne
15
%%% All Rights Reserved.''
16
%%% This software is copyright 2006-2009, ProcessOne.
18
%%% @author Brian Cully <bjc@kublai.com>
19
%%% @version {@vsn}, {@date} {@time}
21
%%% ====================================================================
23
-module(pubsub_subscription).
24
-author("bjc@kublai.com").
33
parse_options_xform/1]).
35
% Internal function also exported for use in transactional bloc from pubsub plugins
36
-export([add_subscription/3,
37
delete_subscription/3,
39
write_subscription/4]).
41
-include("pubsub.hrl").
44
-define(PUBSUB_DELIVER, "pubsub#deliver").
45
-define(PUBSUB_DIGEST, "pubsub#digest").
46
-define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency").
47
-define(PUBSUB_EXPIRE, "pubsub#expire").
48
-define(PUBSUB_INCLUDE_BODY, "pubsub#include_body").
49
-define(PUBSUB_SHOW_VALUES, "pubsub#show-values").
50
-define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type").
51
-define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth").
53
-define(DELIVER_LABEL,
54
"Whether an entity wants to receive or disable notifications").
56
"Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually").
57
-define(DIGEST_FREQUENCY_LABEL,
58
"The minimum number of milliseconds between sending any two notification digests").
60
"The DateTime at which a leased subscription will end or has ended").
61
-define(INCLUDE_BODY_LABEL,
62
"Whether an entity wants to receive an XMPP message body in addition to the payload format").
63
-define(SHOW_VALUES_LABEL,
64
"The presence states for which an entity wants to receive notifications").
65
-define(SUBSCRIPTION_TYPE_LABEL,
66
"Type of notification to receive").
67
-define(SUBSCRIPTION_DEPTH_LABEL,
68
"Depth from subscription for which to receive notifications").
70
-define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away").
71
-define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat").
72
-define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)").
73
-define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)").
74
-define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)").
76
-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL,
77
"Receive notification of new items only").
78
-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL,
79
"Receive notification of new nodes only").
81
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL,
82
"Receive notification from direct child nodes only").
83
-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL,
84
"Receive notification from all descendent nodes").
86
%%====================================================================
88
%%====================================================================
92
subscribe_node(JID, NodeID, Options) ->
93
case catch mnesia:sync_dirty(fun add_subscription/3,
94
[JID, NodeID, Options]) of
95
{'EXIT', {aborted, Error}} -> Error;
96
{error, Error} -> {error, Error};
97
Result -> {result, Result}
100
unsubscribe_node(JID, NodeID, SubID) ->
101
case catch mnesia:sync_dirty(fun delete_subscription/3,
102
[JID, NodeID, SubID]) of
103
{'EXIT', {aborted, Error}} -> Error;
104
{error, Error} -> {error, Error};
105
Result -> {result, Result}
108
get_subscription(JID, NodeID, SubID) ->
109
case catch mnesia:sync_dirty(fun read_subscription/3,
110
[JID, NodeID, SubID]) of
111
{'EXIT', {aborted, Error}} -> Error;
112
{error, Error} -> {error, Error};
113
Result -> {result, Result}
116
set_subscription(JID, NodeID, SubID, Options) ->
117
case catch mnesia:sync_dirty(fun write_subscription/4,
118
[JID, NodeID, SubID, Options]) of
119
{'EXIT', {aborted, Error}} -> Error;
120
{error, Error} -> {error, Error};
121
Result -> {result, Result}
124
get_options_xform(Lang, Options) ->
125
Keys = [deliver, show_values, subscription_type, subscription_depth],
126
XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys],
128
{result, {xmlelement, "x", [{"xmlns", ?NS_XDATA}],
129
[{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
130
[{xmlelement, "value", [],
131
[{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] ++ XFields}}.
133
parse_options_xform(XFields) ->
134
case xml:remove_cdata(XFields) of
136
[{xmlelement, "x", _Attrs, _Els} = XEl] ->
137
case jlib:parse_xdata_submit(XEl) of
138
XData when is_list(XData) ->
139
case set_xoption(XData, []) of
140
Opts when is_list(Opts) -> {result, Opts};
150
%%====================================================================
151
%% Internal functions
152
%%====================================================================
154
case mnesia:create_table(pubsub_subscription,
155
[{disc_copies, [node()]},
156
{attributes, record_info(fields, pubsub_subscription)},
159
{aborted, {already_exists, _}} -> ok;
163
add_subscription(_JID, _NodeID, Options) ->
164
SubID = make_subid(),
165
mnesia:write(#pubsub_subscription{subid = SubID, options = Options}),
168
delete_subscription(_JID, _NodeID, SubID) ->
169
mnesia:delete({pubsub_subscription, SubID}).
171
read_subscription(_JID, _NodeID, SubID) ->
172
case mnesia:read({pubsub_subscription, SubID}) of
174
_ -> {error, notfound}
177
write_subscription(JID, NodeID, SubID, Options) ->
178
case read_subscription(JID, NodeID, SubID) of
179
{error, notfound} -> {error, notfound};
180
Sub -> mnesia:write(Sub#pubsub_subscription{options = Options})
184
{T1, T2, T3} = now(),
185
lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])).
188
%% Subscription XForm processing.
191
%% Return processed options, with types converted and so forth, using
193
set_xoption([], Opts) ->
195
set_xoption([{Var, Value} | T], Opts) ->
196
NewOpts = case var_xfield(Var) of
200
Val = val_xfield(Key, Value),
201
lists:keystore(Key, 1, Opts, {Key, Val})
203
set_xoption(T, NewOpts).
205
%% Return the options list's key for an XForm var.
206
var_xfield(?PUBSUB_DELIVER) -> deliver;
207
var_xfield(?PUBSUB_DIGEST) -> digest;
208
var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency;
209
var_xfield(?PUBSUB_EXPIRE) -> expire;
210
var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body;
211
var_xfield(?PUBSUB_SHOW_VALUES) -> show_values;
212
var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type;
213
var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth;
214
var_xfield(_) -> {error, badarg}.
216
%% Convert Values for option list's Key.
217
val_xfield(deliver, [Val]) -> xopt_to_bool(Val);
218
val_xfield(digest, [Val]) -> xopt_to_bool(Val);
219
val_xfield(digest_frequency, [Val]) -> list_to_integer(Val);
220
val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val);
221
val_xfield(include_body, [Val]) -> xopt_to_bool(Val);
222
val_xfield(show_values, Vals) -> Vals;
223
val_xfield(subscription_type, ["items"]) -> items;
224
val_xfield(subscription_type, ["nodes"]) -> nodes;
225
val_xfield(subscription_depth, ["all"]) -> all;
226
val_xfield(subscription_depth, [Depth]) ->
227
case catch list_to_integer(Depth) of
228
N when is_integer(N) -> N;
229
_ -> {error, ?ERR_NOT_ACCEPTABLE}
232
%% Convert XForm booleans to Erlang booleans.
233
xopt_to_bool("0") -> false;
234
xopt_to_bool("1") -> true;
235
xopt_to_bool("false") -> false;
236
xopt_to_bool("true") -> true;
237
xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
239
%% Return a field for an XForm for Key, with data filled in, if
240
%% applicable, from Options.
241
get_option_xfield(Lang, Key, Options) ->
242
Var = xfield_var(Key),
243
Label = xfield_label(Key),
244
{Type, OptEls} = type_and_options(xfield_type(Key), Lang),
245
Vals = case lists:keysearch(Key, 1, Options) of
247
[tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)];
251
{xmlelement, "field",
252
[{"var", Var}, {"type", Type},
253
{"label", translate:translate(Lang, Label)}],
256
type_and_options({Type, Options}, Lang) ->
257
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
258
type_and_options(Type, _Lang) ->
261
tr_xfield_options({Value, Label}, Lang) ->
262
{xmlelement, "option",
263
[{"label", translate:translate(Lang, Label)}], [{xmlelement, "value", [],
264
[{xmlcdata, Value}]}]}.
266
tr_xfield_values(Value) ->
267
{xmlelement, "value", [], [{xmlcdata, Value}]}.
269
%% Return the XForm variable name for a subscription option key.
270
xfield_var(deliver) -> ?PUBSUB_DELIVER;
271
xfield_var(digest) -> ?PUBSUB_DIGEST;
272
xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY;
273
xfield_var(expire) -> ?PUBSUB_EXPIRE;
274
xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
275
xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
276
xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
277
xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
279
%% Return the XForm variable type for a subscription option key.
280
xfield_type(deliver) -> "boolean";
281
xfield_type(digest) -> "boolean";
282
xfield_type(digest_frequency) -> "text-single";
283
xfield_type(expire) -> "text-single";
284
xfield_type(include_body) -> "boolean";
285
xfield_type(show_values) ->
286
{"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL},
287
{"chat", ?SHOW_VALUE_CHAT_LABEL},
288
{"dnd", ?SHOW_VALUE_DND_LABEL},
289
{"online", ?SHOW_VALUE_ONLINE_LABEL},
290
{"xa", ?SHOW_VALUE_XA_LABEL}]};
291
xfield_type(subscription_type) ->
292
{"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
293
{"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
294
xfield_type(subscription_depth) ->
295
{"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
296
{"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
298
%% Return the XForm variable label for a subscription option key.
299
xfield_label(deliver) -> ?DELIVER_LABEL;
300
xfield_label(digest) -> ?DIGEST_LABEL;
301
xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL;
302
xfield_label(expire) -> ?EXPIRE_LABEL;
303
xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
304
xfield_label(show_values) -> ?SHOW_VALUES_LABEL;
305
xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL;
306
xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
308
%% Return the XForm value for a subscription option key.
309
xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
310
xfield_val(digest, Val) -> [bool_to_xopt(Val)];
311
xfield_val(digest_frequency, Val) -> [integer_to_list(Val)];
312
xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)];
313
xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
314
xfield_val(show_values, Val) -> Val;
315
xfield_val(subscription_type, items) -> ["items"];
316
xfield_val(subscription_type, nodes) -> ["nodes"];
317
xfield_val(subscription_depth, all) -> ["all"];
318
xfield_val(subscription_depth, N) -> [integer_to_list(N)].
320
%% Convert erlang booleans to XForms.
321
bool_to_xopt(false) -> "false";
322
bool_to_xopt(true) -> "true".