25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-26 16:26:24 +01:00

Port pubsub odbc backend to exmpp.

Warning: A work in progress, isn't working yet!

SVN Revision: 2541
This commit is contained in:
Pablo Polvorin 2009-08-26 20:27:57 +00:00
parent ba3a45452e
commit 944dd1cc7f
9 changed files with 6898 additions and 1 deletions

View File

@ -25,7 +25,7 @@ ERLBEHAVBEAMS = $(addprefix $(OUTDIR)/,$(ERLBEHAVS:.erl=.beam))
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
all: $(ERLBEHAVBEAMS) $(BEAMS)
all: mod_pubsub_odbc.erl $(ERLBEHAVBEAMS) $(BEAMS)
$(BEAMS): $(ERLBEHAVBEAMS)
@ -38,6 +38,9 @@ clean:
distclean: clean
rm -f Makefile
mod_pubsub_odbc.erl:
patch -o mod_pubsub_odbc.erl mod_pubsub.erl pubsub_odbc.patch
TAGS:
etags *.erl

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,183 @@
%%% ====================================================================
%%% ``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.
%%%
%%% @copyright 2006-2009 ProcessOne
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(node_flat_odbc).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/2,
delete_node/1,
purge_node/2,
subscribe_node/8,
unsubscribe_node/4,
publish_item/6,
delete_item/4,
remove_extra_items/3,
get_entity_affiliations/2,
get_node_affiliations/1,
get_affiliation/2,
set_affiliation/3,
get_entity_subscriptions/2,
get_node_subscriptions/1,
get_subscriptions/2,
set_subscriptions/4,
get_pending_nodes/2,
get_states/1,
get_state/2,
set_state/1,
get_items/6,
get_items/2,
get_item/7,
get_item/2,
set_item/1,
get_item_name/3
]).
init(Host, ServerHost, Opts) ->
node_hometree_odbc:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_hometree_odbc:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
{persist_items, true},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, open},
{roster_groups_allowed, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
{presence_based_delivery, false},
{odbc, true},
{rsm, true}].
features() ->
node_hometree_odbc:features().
%% use same code as node_hometree_odbc, but do not limite node to
%% the home/localhost/user/... hierarchy
%% any node is allowed
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
LOwner = jlib:short_prepd_jid(Owner),
Allowed = case LOwner of
{undefined, Host, undefined} ->
true; % pubsub service always allowed
_ ->
{LU, LS, LR} = LOwner,
acl:match_rule(ServerHost, Access, exmpp_jid:make(LU, LS, LR)) =:= allow
end,
{result, Allowed}.
create_node(NodeId, Owner) ->
node_hometree_odbc:create_node(NodeId, Owner).
delete_node(Removed) ->
node_hometree_odbc:delete_node(Removed).
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree_odbc:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_hometree_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
node_hometree_odbc:remove_extra_items(NodeId, MaxItems, ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
node_hometree_odbc:delete_item(NodeId, Publisher, PublishModel, ItemId).
purge_node(NodeId, Owner) ->
node_hometree_odbc:purge_node(NodeId, Owner).
get_entity_affiliations(Host, Owner) ->
node_hometree_odbc:get_entity_affiliations(Host, Owner).
get_node_affiliations(NodeId) ->
node_hometree_odbc:get_node_affiliations(NodeId).
get_affiliation(NodeId, Owner) ->
node_hometree_odbc:get_affiliation(NodeId, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation).
get_entity_subscriptions(Host, Owner) ->
node_hometree_odbc:get_entity_subscriptions(Host, Owner).
get_node_subscriptions(NodeId) ->
node_hometree_odbc:get_node_subscriptions(NodeId).
get_subscriptions(NodeId, Owner) ->
node_hometree_odbc:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
node_hometree_odbc:set_subscriptions(NodeId, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree_odbc:get_pending_nodes(Host, Owner).
get_states(NodeId) ->
node_hometree_odbc:get_states(NodeId).
get_state(NodeId, JID) ->
node_hometree_odbc:get_state(NodeId, JID).
set_state(State) ->
node_hometree_odbc:set_state(State).
get_items(NodeId, From) ->
node_hometree_odbc:get_items(NodeId, From).
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_hometree_odbc:get_item(NodeId, ItemId).
get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree_odbc:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
node_hometree_odbc:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree_odbc:get_item_name(Host, Node, Id).

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,327 @@
%%% ====================================================================
%%% ``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.
%%%
%%%
%%% @copyright 2006-2009 ProcessOne
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @doc The module <strong>{@module}</strong> is the pep PubSub plugin.
%%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p>
-module(node_pep_odbc).
-author('christophe.romain@process-one.net').
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("pubsub.hrl").
-define(PUBSUB, mod_pubsub_odbc).
-behaviour(gen_pubsub_node).
%% API definition
-export([init/3, terminate/2,
options/0, features/0,
create_node_permission/6,
create_node/2,
delete_node/1,
purge_node/2,
subscribe_node/8,
unsubscribe_node/4,
publish_item/6,
delete_item/4,
remove_extra_items/3,
get_entity_affiliations/2,
get_node_affiliations/1,
get_affiliation/2,
set_affiliation/3,
get_entity_subscriptions/2,
get_entity_subscriptions_for_send_last/2,
get_node_subscriptions/1,
get_subscriptions/2,
set_subscriptions/4,
get_pending_nodes/2,
get_states/1,
get_state/2,
set_state/1,
get_items/7,
get_items/6,
get_items/3,
get_items/2,
get_item/7,
get_item/2,
set_item/1,
get_item_name/3,
get_last_items/3
]).
init(Host, ServerHost, Opts) ->
node_hometree_odbc:init(Host, ServerHost, Opts),
complain_if_modcaps_disabled(ServerHost),
ok.
terminate(Host, ServerHost) ->
node_hometree_odbc:terminate(Host, ServerHost),
ok.
options() ->
[{odbc, true},
{node_type, pep},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, false},
{persist_items, false},
{max_items, ?MAXITEMS div 2},
{subscribe, true},
{access_model, presence},
{roster_groups_allowed, []},
{publish_model, publishers},
{max_payload_size, ?MAX_PAYLOAD_SIZE},
{send_last_published_item, on_sub_and_presence},
{deliver_notifications, true},
{presence_based_delivery, true}].
features() ->
["create-nodes", %*
"auto-create", %*
"auto-subscribe", %*
"delete-nodes", %*
"delete-items", %*
"filtered-notifications", %*
"modify-affiliations",
"outcast-affiliation",
"persistent-items",
"publish", %*
"purge-nodes",
"retract-items",
"retrieve-affiliations",
"retrieve-items", %*
"retrieve-subscriptions",
"subscribe" %*
].
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
LOwner = jlib:short_prepd_jid(Owner),
{User, Server, Resource} = LOwner,
Allowed = case LOwner of
{undefined, Host, undefined} ->
true; % pubsub service always allowed
_ ->
JID = exmpp_jid:make(User, Server, Resource),
case acl:match_rule(ServerHost, Access, JID) of
allow ->
case Host of
{User, Server, _} -> true;
_ -> false
end;
E ->
?DEBUG("Create not allowed : ~p~n", [E]),
false
end
end,
{result, Allowed}.
create_node(NodeId, Owner) ->
case node_hometree_odbc:create_node(NodeId, Owner) of
{result, _} -> {result, []};
Error -> Error
end.
delete_node(Removed) ->
case node_hometree_odbc:delete_node(Removed) of
{result, {_, _, Removed}} -> {result, {[], Removed}};
Error -> Error
end.
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree_odbc:subscribe_node(
NodeId, Sender, Subscriber, AccessModel, SendLast,
PresenceSubscription, RosterGroup, Options).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
case node_hometree_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of
{error, Error} -> {error, Error};
{result, _} -> {result, []}
end.
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
node_hometree_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload).
remove_extra_items(NodeId, MaxItems, ItemIds) ->
node_hometree_odbc:remove_extra_items(NodeId, MaxItems, ItemIds).
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
node_hometree_odbc:delete_item(NodeId, Publisher, PublishModel, ItemId).
purge_node(NodeId, Owner) ->
node_hometree_odbc:purge_node(NodeId, Owner).
get_entity_affiliations(_Host, Owner) ->
OwnerKey = jlib:short_prepd_bare_jid(Owner),
node_hometree_odbc:get_entity_affiliations(OwnerKey, Owner).
get_node_affiliations(NodeId) ->
node_hometree_odbc:get_node_affiliations(NodeId).
get_affiliation(NodeId, Owner) ->
node_hometree_odbc:get_affiliation(NodeId, Owner).
set_affiliation(NodeId, Owner, Affiliation) ->
node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation).
get_entity_subscriptions(_Host, Owner) ->
SubKey = jlib:short_prepd_jid(Owner),
GenKey = jlib:short_prepd_bare_jid(SubKey),
Host = ?PUBSUB:escape(element(2, SubKey)),
SJ = node_hometree_odbc:encode_jid(SubKey),
GJ = node_hometree_odbc:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
["select host, node, type, i.nodeid, jid, subscription "
"from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid "
"and jid like '", GJ, "%' "
"and host like '%@", Host, "';"];
_ ->
["select host, node, type, i.nodeid, jid, subscription "
"from pubsub_state i, pubsub_node n "
"where i.nodeid = n.nodeid "
"and jid in ('", SJ, "', '", GJ, "') "
"and host like '%@", Host, "';"]
end,
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
{selected, ["host", "node", "type", "nodeid", "jid", "subscription"], RItems} ->
lists:map(fun({H, N, T, I, J, S}) ->
O = node_hometree_odbc:decode_jid(H),
Node = nodetree_odbc:raw_to_node(O, {N, "", T, I}),
{Node, node_hometree_odbc:decode_subscription(S), node_hometree_odbc:decode_jid(J)}
end, RItems);
_ ->
[]
end,
{result, Reply}.
get_entity_subscriptions_for_send_last(_Host, Owner) ->
SubKey = jlib:short_prepd_jid(Owner),
GenKey = jlib:short_prepd_bare_jid(SubKey),
Host = ?PUBSUB:escape(element(2, SubKey)),
SJ = node_hometree_odbc:encode_jid(SubKey),
GJ = node_hometree_odbc:encode_jid(GenKey),
Query = case SubKey of
GenKey ->
["select host, node, type, i.nodeid, jid, subscription "
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid "
"and name='send_last_published_item' and val='on_sub_and_presence' "
"and jid like '", GJ, "%' "
"and host like '%@", Host, "';"];
_ ->
["select host, node, type, i.nodeid, jid, subscription "
"from pubsub_state i, pubsub_node n, pubsub_node_option o "
"where i.nodeid = n.nodeid and n.nodeid = o.nodeid "
"and name='send_last_published_item' and val='on_sub_and_presence' "
"and jid in ('", SJ, "', '", GJ, "') "
"and host like '%@", Host, "';"]
end,
Reply = case catch ejabberd_odbc:sql_query_t(Query) of
{selected, ["host", "node", "type", "nodeid", "jid", "subscription"], RItems} ->
lists:map(fun({H, N, T, I, J, S}) ->
O = node_hometree_odbc:decode_jid(H),
Node = nodetree_odbc:raw_to_node(O, {N, "", T, I}),
{Node, node_hometree_odbc:decode_subscription(S), node_hometree_odbc:decode_jid(J)}
end, RItems);
_ ->
[]
end,
{result, Reply}.
get_node_subscriptions(NodeId) ->
%% note: get_node_subscriptions is used for broadcasting
%% there should not have any subscriptions
%% but that call returns also all subscription to none
%% and this is required for broadcast to occurs
%% DO NOT REMOVE
node_hometree_odbc:get_node_subscriptions(NodeId).
get_subscriptions(NodeId, Owner) ->
node_hometree_odbc:get_subscriptions(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
node_hometree_odbc:set_subscriptions(NodeId, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree_odbc:get_pending_nodes(Host, Owner).
get_states(NodeId) ->
node_hometree_odbc:get_states(NodeId).
get_state(NodeId, JID) ->
node_hometree_odbc:get_state(NodeId, JID).
set_state(State) ->
node_hometree_odbc:set_state(State).
get_items(NodeId, From) ->
node_hometree_odbc:get_items(NodeId, From).
get_items(NodeId, From, RSM) ->
node_hometree_odbc:get_items(NodeId, From, RSM).
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM).
get_last_items(NodeId, JID, Count) ->
node_hometree_odbc:get_last_items(NodeId, JID, Count).
get_item(NodeId, ItemId) ->
node_hometree_odbc:get_item(NodeId, ItemId).
get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
node_hometree_odbc:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
set_item(Item) ->
node_hometree_odbc:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree_odbc:get_item_name(Host, Node, Id).
%%%
%%% Internal
%%%
%% @doc Check mod_caps is enabled, otherwise show warning.
%% The PEP plugin for mod_pubsub requires mod_caps to be enabled in the host.
%% Check that the mod_caps module is enabled in that Jabber Host
%% If not, show a warning message in the ejabberd log file.
complain_if_modcaps_disabled(ServerHost) ->
Modules = ejabberd_config:get_local_option({modules, ServerHost}),
ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules],
case ModCaps of
[] ->
?WARNING_MSG("The PEP plugin is enabled in mod_pubsub of host ~p. "
"This plugin requires mod_caps to be enabled, "
"but it isn't.", [ServerHost]);
_ ->
ok
end.

View File

@ -0,0 +1,357 @@
%%% ====================================================================
%%% ``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.
%%%
%%%
%%% @copyright 2006-2009 ProcessOne
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @doc The module <strong>{@module}</strong> is the default PubSub node tree plugin.
%%% <p>It is used as a default for all unknown PubSub node type. It can serve
%%% as a developer basis and reference to build its own custom pubsub node tree
%%% types.</p>
%%% <p>PubSub node tree plugins are using the {@link gen_nodetree} behaviour.</p>
%%% <p><strong>The API isn't stabilized yet</strong>. The pubsub plugin
%%% development is still a work in progress. However, the system is already
%%% useable and useful as is. Please, send us comments, feedback and
%%% improvements.</p>
-module(nodetree_tree_odbc).
-author('christophe.romain@process-one.net').
-include_lib("stdlib/include/qlc.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-include("pubsub.hrl").
-define(PUBSUB, mod_pubsub_odbc).
-define(PLUGIN_PREFIX, "node_").
-behaviour(gen_pubsub_nodetree).
-export([init/3,
terminate/2,
options/0,
set_node/1,
get_node/3,
get_node/2,
get_node/1,
get_nodes/2,
get_nodes/1,
get_parentnodes/3,
get_parentnodes_tree/3,
get_subnodes/3,
get_subnodes_tree/3,
create_node/5,
delete_node/2
]).
-export([raw_to_node/2]).
%% ================
%% API definition
%% ================
%% @spec (Host, ServerHost, Opts) -> any()
%% Host = mod_pubsub:host()
%% ServerHost = host()
%% Opts = list()
%% @doc <p>Called during pubsub modules initialisation. Any pubsub plugin must
%% implement this function. It can return anything.</p>
%% <p>This function is mainly used to trigger the setup task necessary for the
%% plugin. It can be used for example by the developer to create the specific
%% module database schema if it does not exists yet.</p>
init(_Host, _ServerHost, _Opts) ->
ok.
terminate(_Host, _ServerHost) ->
ok.
%% @spec () -> [Option]
%% Option = mod_pubsub:nodetreeOption()
%% @doc Returns the default pubsub node tree options.
options() ->
[{virtual_tree, false},
{odbc, true}].
%% @spec (Host, Node) -> pubsubNode() | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
get_node(Host, Node, _From) ->
get_node(Host, Node).
get_node(Host, Node) ->
H = ?PUBSUB:escape(Host),
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
case catch ejabberd_odbc:sql_query_t(
["select node, parent, type, nodeid "
"from pubsub_node "
"where host='", H, "' and node='", N, "';"])
of
{selected, ["node", "parent", "type", "nodeid"], [RItem]} ->
raw_to_node(Host, RItem);
{'EXIT', _Reason} ->
{error, 'internal_server_error'};
_ ->
{error, 'item_not_found'}
end.
get_node(NodeId) ->
case catch ejabberd_odbc:sql_query_t(
["select host, node, parent, type "
"from pubsub_node "
"where nodeid='", NodeId, "';"])
of
{selected, ["host", "node", "parent", "type"], [{Host, Node, Parent, Type}]} ->
raw_to_node(Host, {Node, Parent, Type, NodeId});
{'EXIT', _Reason} ->
{error, 'internal_server_error'};
_ ->
{error, 'item_not_found'}
end.
%% @spec (Host) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
get_nodes(Host, _From) ->
get_nodes(Host).
get_nodes(Host) ->
H = ?PUBSUB:escape(Host),
case catch ejabberd_odbc:sql_query_t(
["select node, parent, type, nodeid "
"from pubsub_node "
"where host='", H, "';"])
of
{selected, ["node", "parent", "type", "nodeid"], RItems} ->
lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
_ ->
[]
end.
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% Depth = integer()
%% Record = pubsubNode()
%% @doc <p>Default node tree does not handle parents, return empty list.</p>
get_parentnodes(_Host, _Node, _From) ->
[].
%% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
%% Depth = integer()
%% Record = pubsubNode()
%% @doc <p>Default node tree does not handle parents, return a list
%% containing just this node.</p>
get_parentnodes_tree(Host, Node, From) ->
case get_node(Host, Node, From) of
N when is_record(N, pubsub_node) -> [{0, [N]}];
Error -> Error
end.
get_subnodes(Host, Node, _From) ->
get_subnodes(Host, Node).
get_subnodes(Host, Node) ->
H = ?PUBSUB:escape(Host),
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
case catch ejabberd_odbc:sql_query_t(
["select node, parent, type, nodeid "
"from pubsub_node "
"where host='", H, "' and parent='", N, "';"])
of
{selected, ["node", "parent", "type", "nodeid"], RItems} ->
lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
_ ->
[]
end.
%% @spec (Host, Index) -> [pubsubNodeIdx()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
%% From = mod_pubsub:jid()
get_subnodes_tree(Host, Node, _From) ->
get_subnodes_tree(Host, Node).
get_subnodes_tree(Host, Node) ->
H = ?PUBSUB:escape(Host),
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
case catch ejabberd_odbc:sql_query_t(
["select node, parent, type, nodeid "
"from pubsub_node "
"where host='", H, "' and node like '", N, "%';"])
of
{selected, ["node", "parent", "type", "nodeid"], RItems} ->
lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems);
_ ->
[]
end.
%% @spec (Host, Node, Type, Owner, Options) -> ok | {error, Reason}
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
%% NodeType = mod_pubsub:nodeType()
%% Owner = mod_pubsub:jid()
%% Options = list()
create_node(Host, Node, Type, _Owner, Options) ->
case nodeid(Host, Node) of
{error, 'item_not_found'} ->
{ParentNode, ParentExists} = case Host of
{_U, _S, _R} ->
%% This is special case for PEP handling
%% PEP does not uses hierarchy
{[], true};
_ ->
case lists:sublist(Node, length(Node) - 1) of
[] ->
{[], true};
Parent ->
case nodeid(Host, Parent) of
{result, _} -> {Parent, true};
_ -> {Parent, false}
end
end
end,
case ParentExists of
true ->
case set_node(#pubsub_node{
nodeid={Host, Node},
parents=[ParentNode],
type=Type,
options=Options}) of
{result, NodeId} -> {ok, NodeId};
Other -> Other
end;
false ->
%% Requesting entity is prohibited from creating nodes
{error, 'forbidden'}
end;
{result, _} ->
%% NodeID already exists
{error, 'conflict'};
Error ->
Error
end.
%% @spec (Host, Node) -> [mod_pubsub:node()]
%% Host = mod_pubsub:host() | mod_pubsub:jid()
%% Node = mod_pubsub:pubsubNode()
delete_node(Host, Node) ->
H = ?PUBSUB:escape(Host),
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
Removed = get_subnodes_tree(Host, Node),
catch ejabberd_odbc:sql_query_t(
["delete from pubsub_node "
"where host='", H, "' and node like '", N, "%';"]),
Removed.
%% helpers
raw_to_node(Host, {Node, Parent, Type, NodeId}) ->
Options = case catch ejabberd_odbc:sql_query_t(
["select name,val "
"from pubsub_node_option "
"where nodeid='", NodeId, "';"])
of
{selected, ["name", "val"], ROptions} ->
DbOpts = lists:map(fun({Key, Value}) ->
RKey = list_to_atom(Key),
Tokens = element(2, erl_scan:string(Value++".")),
RValue = element(2, erl_parse:parse_term(Tokens)),
{RKey, RValue}
end, ROptions),
Module = list_to_atom(?PLUGIN_PREFIX++Type),
StdOpts = Module:options(),
lists:foldl(fun({Key, Value}, Acc)->
lists:keyreplace(Key, 1, Acc, {Key, Value})
end, StdOpts, DbOpts);
_ ->
[]
end,
#pubsub_node{
nodeid = {Host, string_to_node(Host, Node)},
parents = [string_to_node(Host, Parent)],
id = NodeId,
type = Type,
options = Options}.
%% @spec (NodeRecord) -> ok | {error, Reason}
%% Record = mod_pubsub:pubsub_node()
set_node(Record) ->
{Host, Node} = Record#pubsub_node.nodeid,
[Parent] = Record#pubsub_node.parents,
Type = Record#pubsub_node.type,
H = ?PUBSUB:escape(Host),
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
P = ?PUBSUB:escape(?PUBSUB:node_to_string(Parent)),
NodeId = case nodeid(Host, Node) of
{result, OldNodeId} ->
catch ejabberd_odbc:sql_query_t(
["delete from pubsub_node_option "
"where nodeid='", OldNodeId, "';"]),
catch ejabberd_odbc:sql_query_t(
["update pubsub_node "
"set host='", H, "' "
"node='", N, "' "
"parent='", P, "' "
"type='", Type, "' "
"where nodeid='", OldNodeId, "';"]),
OldNodeId;
_ ->
catch ejabberd_odbc:sql_query_t(
["insert into pubsub_node(host, node, parent, type) "
"values('", H, "', '", N, "', '", P, "', '", Type, "');"]),
case nodeid(Host, Node) of
{result, NewNodeId} -> NewNodeId;
_ -> none % this should not happen
end
end,
case NodeId of
none ->
{error, 'internal_server_error'};
_ ->
lists:foreach(fun({Key, Value}) ->
SKey = atom_to_list(Key),
SValue = ?PUBSUB:escape(lists:flatten(io_lib:fwrite("~p",[Value]))),
catch ejabberd_odbc:sql_query_t(
["insert into pubsub_node_option(nodeid, name, val) "
"values('", NodeId, "', '", SKey, "', '", SValue, "');"])
end, Record#pubsub_node.options),
{result, NodeId}
end.
nodeid(Host, Node) ->
H = ?PUBSUB:escape(Host),
N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)),
case catch ejabberd_odbc:sql_query_t(
["select nodeid "
"from pubsub_node "
"where host='", H, "' and node='", N, "';"])
of
{selected, ["nodeid"], [{NodeId}]} ->
{result, NodeId};
{'EXIT', _Reason} ->
{error, 'internal_server_error'};
_ ->
{error, 'item_not_found'}
end.
string_to_node({_, _, _}, Node) -> Node;
string_to_node(_, Node) -> ?PUBSUB:string_to_node(Node).

View File

@ -0,0 +1,138 @@
%%% ====================================================================
%%% ``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 Pablo Polvorin <pablo.polvorin@process-one.net>
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(pubsub_db_odbc).
-author("pablo.polvorin@process-one.net").
-include("pubsub.hrl").
-export([add_subscription/1,
read_subscription/1,
delete_subscription/1,
update_subscription/1]).
%% Those -spec lines produce errors in old Erlang versions.
%% They can be enabled again in ejabberd 3.0 because it uses R12B or higher.
-spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound.
read_subscription(SubID) ->
case ejabberd_odbc:sql_query_t(
["select opt_name, opt_value "
"from pubsub_subscription_opt "
"where subid = '", ejabberd_odbc:escape(SubID), "'"]) of
{selected, ["opt_name", "opt_value"], []} ->
notfound;
{selected, ["opt_name", "opt_value"], Options} ->
{ok, #pubsub_subscription{subid = SubID,
options = lists:map(fun subscription_opt_from_odbc/1, Options)}}
end.
-spec delete_subscription(SubID :: string()) -> ok.
delete_subscription(SubID) ->
ejabberd_odbc:sql_query_t(["delete from pubsub_subscription_opt "
"where subid = '", ejabberd_odbc:escape(SubID), "'"]),
ok.
-spec update_subscription(#pubsub_subscription{}) -> ok .
update_subscription(#pubsub_subscription{subid = SubId} = Sub) ->
delete_subscription(SubId),
add_subscription(Sub).
-spec add_subscription(#pubsub_subscription{}) -> ok.
add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) ->
EscapedSubId = ejabberd_odbc:escape(SubId),
lists:foreach(fun(Opt) ->
{OdbcOptName, OdbcOptValue} = subscription_opt_to_odbc(Opt),
ejabberd_odbc:sql_query_t(
["insert into pubsub_subscription_opt(subid, opt_name, opt_value)"
"values ('", EscapedSubId, "','", OdbcOptName, "','", OdbcOptValue, "')"])
end, Opts),
ok.
%% -------------- Internal utilities -----------------------
subscription_opt_from_odbc({"DELIVER", Value}) ->
{deliver, odbc_to_boolean(Value)};
subscription_opt_from_odbc({"DIGEST", Value}) ->
{digest, odbc_to_boolean(Value)};
subscription_opt_from_odbc({"DIGEST_FREQUENCY", Value}) ->
{digest_frequency, odbc_to_integer(Value)};
subscription_opt_from_odbc({"EXPIRE", Value}) ->
{expire, odbc_to_timestamp(Value)};
subscription_opt_from_odbc({"INCLUDE_BODY", Value}) ->
{include_body, odbc_to_boolean(Value)};
%%TODO: might be > than 1 show_values value??.
%% need to use compact all in only 1 opt.
subscription_opt_from_odbc({"SHOW_VALUES", Value}) ->
{show_values, Value};
subscription_opt_from_odbc({"SUBSCRIPTION_TYPE", Value}) ->
{subscription_type, case Value of
"items" -> items;
"nodes" -> nodes
end};
subscription_opt_from_odbc({"SUBSCRIPTION_DEPTH", Value}) ->
{subscription_depth, case Value of
"all" -> all;
N -> odbc_to_integer(N)
end}.
subscription_opt_to_odbc({deliver, Bool}) ->
{"DELIVER", boolean_to_odbc(Bool)};
subscription_opt_to_odbc({digest, Bool}) ->
{"DIGEST", boolean_to_odbc(Bool)};
subscription_opt_to_odbc({digest_frequency, Int}) ->
{"DIGEST_FREQUENCY", integer_to_odbc(Int)};
subscription_opt_to_odbc({expire, Timestamp}) ->
{"EXPIRE", timestamp_to_odbc(Timestamp)};
subscription_opt_to_odbc({include_body, Bool}) ->
{"INCLUDE_BODY", boolean_to_odbc(Bool)};
subscription_opt_to_odbc({show_values, Values}) ->
{"SHOW_VALUES", Values};
subscription_opt_to_odbc({subscription_type, Type}) ->
{"SUBSCRIPTION_TYPE", case Type of
items -> "items";
nodes -> "nodes"
end};
subscription_opt_to_odbc({subscription_depth, Depth}) ->
{"SUBSCRIPTION_DEPTH", case Depth of
all -> "all";
N -> integer_to_odbc(N)
end}.
integer_to_odbc(N) ->
integer_to_list(N).
boolean_to_odbc(true) -> "1";
boolean_to_odbc(false) -> "0".
timestamp_to_odbc(T) -> jlib:now_to_utc_string(T).
odbc_to_integer(N) -> list_to_integer(N).
odbc_to_boolean(B) -> B == "1".
odbc_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T).

View File

@ -0,0 +1,672 @@
--- mod_pubsub.erl 2009-08-25 17:29:57.000000000 -0300
+++ mod_pubsub_odbc.erl 2009-08-26 12:07:05.000000000 -0300
@@ -45,7 +45,7 @@
%%% TODO
%%% plugin: generate Reply (do not use broadcast atom anymore)
--module(mod_pubsub).
+-module(mod_pubsub_odbc).
-author('christophe.romain@process-one.net').
-version('1.12-06').
@@ -58,9 +58,9 @@
-include("adhoc.hrl").
-include("pubsub.hrl").
--define(STDTREE, "tree").
--define(STDNODE, "flat").
--define(PEPNODE, "pep").
+-define(STDTREE, "tree_odbc").
+-define(STDNODE, "flat_odbc").
+-define(PEPNODE, "pep_odbc").
%% exports for hooks
-export([presence_probe/3,
@@ -105,7 +105,8 @@
string_to_subscription/1,
string_to_affiliation/1,
extended_error/2,
- extended_error/3
+ extended_error/3,
+ escape/1
]).
%% API and gen_server callbacks
@@ -124,7 +125,7 @@
-export([send_loop/1
]).
--define(PROCNAME, ejabberd_mod_pubsub).
+-define(PROCNAME, ejabberd_mod_pubsub_odbc).
-define(PLUGIN_PREFIX, "node_").
-define(TREE_PREFIX, "nodetree_").
@@ -221,8 +222,6 @@
ok
end,
ejabberd_router:register_route(Host),
- update_node_database(Host, ServerHost),
- update_state_database(Host, ServerHost),
init_nodes(Host, ServerHost),
State = #state{host = Host,
server_host = ServerHost,
@@ -277,177 +276,7 @@
create_node(Host, ServerHost, ["home", ServerHost], service_jid(Host), "hometree"),
ok.
-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 node pubsub tables",[]),
- F = fun() ->
- {Result, LastIdx} = lists:foldl(
- fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, {RecList, NodeIdx}) ->
- ItemsList =
- lists:foldl(
- fun({item, IID, Publisher, Payload}, Acc) ->
- C = {unknown, Publisher},
- M = {now(), Publisher},
- mnesia:write(
- #pubsub_item{itemid = {IID, NodeIdx},
- creation = C,
- modification = M,
- payload = Payload}),
- [{Publisher, IID} | Acc]
- end, [], Items),
- Owners =
- dict:fold(
- fun(JID, {entity, Aff, Sub}, Acc) ->
- UsrItems =
- lists:foldl(
- fun({P, I}, IAcc) ->
- case P of
- JID -> [I | IAcc];
- _ -> IAcc
- end
- end, [], ItemsList),
- mnesia:write({pubsub_state,
- {JID, NodeIdx},
- UsrItems,
- Aff,
- Sub}),
- case Aff of
- owner -> [JID | Acc];
- _ -> Acc
- end
- end, [], Entities),
- mnesia:delete({pubsub_node, NodeId}),
- {[#pubsub_node{nodeid = NodeId,
- id = NodeIdx,
- parents = [element(2, ParentId)],
- owners = Owners,
- options = Options} |
- RecList], NodeIdx + 1}
- end, {[], 1},
- mnesia:match_object(
- {pubsub_node, {Host, '_'}, '_', '_'})),
- mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []}),
- Result
- end,
- {atomic, NewRecords} = mnesia:transaction(F),
- {atomic, ok} = mnesia:delete_table(pubsub_node),
- {atomic, ok} = mnesia:create_table(pubsub_node,
- [{disc_copies, [node()]},
- {attributes, record_info(fields, pubsub_node)}]),
- FNew = fun() -> lists:foreach(fun(Record) ->
- mnesia:write(Record)
- end, NewRecords)
- end,
- case mnesia:transaction(FNew) of
- {atomic, Result} ->
- ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]);
- {aborted, 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}) ->
- #pubsub_node{
- nodeid = NodeId,
- id = 0,
- parents = [Parent],
- type = Type,
- owners = Owners,
- options = Options}
- end,
- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
- FNew = fun() ->
- LastIdx = lists:foldl(fun(#pubsub_node{nodeid = NodeId} = PubsubNode, NodeIdx) ->
- mnesia:write(PubsubNode#pubsub_node{id = NodeIdx}),
- lists:foreach(fun(#pubsub_state{stateid = StateId} = State) ->
- {JID, _} = StateId,
- mnesia:delete({pubsub_state, StateId}),
- mnesia:write(State#pubsub_state{stateid = {JID, NodeIdx}})
- end, mnesia:match_object(#pubsub_state{stateid = {'_', NodeId}, _ = '_'})),
- lists:foreach(fun(#pubsub_item{itemid = ItemId} = Item) ->
- {IID, _} = ItemId,
- {M1, M2} = Item#pubsub_item.modification,
- {C1, C2} = Item#pubsub_item.creation,
- mnesia:delete({pubsub_item, ItemId}),
- mnesia:write(Item#pubsub_item{itemid = {IID, NodeIdx},
- modification = {M2, M1},
- creation = {C2, C1}})
- end, mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'})),
- NodeIdx + 1
- end, 1, mnesia:match_object(
- {pubsub_node, {Host, '_'}, '_', '_', '_', '_', '_'})
- ++ mnesia:match_object(
- {pubsub_node, {{'_', ServerHost, '_'}, '_'}, '_', '_', '_', '_', '_'})),
- mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []})
- end,
- case mnesia:transaction(FNew) of
- {atomic, Result} ->
- rename_default_nodeplugin(),
- ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]);
- {aborted, Reason} ->
- ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason])
- end;
- [nodeid, id, parent, type, owners, options] ->
- F = fun({pubsub_node, NodeId, Id, Parent, Type, Owners, Options}) ->
- #pubsub_node{
- nodeid = NodeId,
- id = Id,
- parents = [Parent],
- type = Type,
- owners = Owners,
- options = Options}
- end,
- mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]),
- rename_default_nodeplugin();
- _ ->
- ok
- end.
-rename_default_nodeplugin() ->
- lists:foreach(fun(Node) ->
- mnesia:dirty_write(Node#pubsub_node{type = "hometree"})
- end, mnesia:dirty_match_object(#pubsub_node{type = "default", _ = '_'})).
-
-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
- end.
send_queue(State, Msg) ->
Pid = State#state.send_loop,
@@ -471,17 +300,15 @@
%% for each node From is subscribed to
%% and if the node is so configured, send the last published item to From
lists:foreach(fun(PType) ->
- {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]),
+ Subscriptions = case catch node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]) of
+ {result, S} -> S;
+ _ -> []
+ end,
lists:foreach(
fun({Node, subscribed, _, SubJID}) ->
if (SubJID == LJID) or (SubJID == BJID) ->
- #pubsub_node{options = Options, type = Type, id = NodeId} = Node,
- case get_option(Options, send_last_published_item) of
- on_sub_and_presence ->
- send_items(Host, Node, NodeId, Type, SubJID, last);
- _ ->
- ok
- end;
+ #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId} = Node,
+ send_items(H, N, NodeId, Type, SubJID, last);
true ->
% resource not concerned about that subscription
ok
@@ -808,10 +635,10 @@
{result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Subscriber]),
lists:foreach(fun
({Node, subscribed, _, JID}) ->
- #pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId} = Node,
+ #pubsub_node{options = Options, type = Type, id = NodeId} = Node,
case get_option(Options, access_model) of
presence ->
- case lists:member(BJID, Owners) of
+ case lists:member(BJID, node_owners(Host, Type, NodeId)) of
true ->
node_action(Host, Type, unsubscribe_node, [NodeId, Subscriber, JID, all]);
false ->
@@ -929,11 +756,12 @@
end,
ejabberd_router:route(To, From, Res);
#iq{type = get, ns = ?NS_DISCO_ITEMS,
- payload = SubEl} ->
+ payload = SubEl} = IQ ->
QAttrs = SubEl#xmlel.attrs,
Node = exmpp_xml:get_attribute_from_list_as_list(QAttrs,
'node', ""),
- Res = case iq_disco_items(Host, Node, From) of
+ Rsm = jlib:rsm_decode(IQ),
+ Res = case iq_disco_items(Host, Node, From, Rsm) of
{result, IQRes} ->
Result = #xmlel{ns = ?NS_DISCO_ITEMS,
name = 'query', attrs = QAttrs,
@@ -1036,7 +864,7 @@
[] ->
["leaf"]; %% No sub-nodes: it's a leaf node
_ ->
- case node_call(Type, get_items, [NodeId, From]) of
+ case node_call(Type, get_items, [NodeId, From, none]) of
{result, []} -> ["collection"];
{result, _} -> ["leaf", "collection"];
_ -> []
@@ -1052,8 +880,9 @@
[];
true ->
[#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s)]} |
- lists:map(fun(T) ->
- #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s++"#"++T)]}
+ lists:map(fun
+ ("rsm") -> #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_RSM_s)]};
+ (T) -> #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s++"#"++T)]}
end, features(Type))]
end,
%% TODO: add meta-data info (spec section 5.4)
@@ -1081,14 +910,15 @@
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_DISCO_ITEMS_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_VCARD_s)]}] ++
- lists:map(fun(Feature) ->
- #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s++"#"++Feature)]}
+ lists:map(fun
+ ("rsm") -> #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_RSM_s)]};
+ (Feature) -> #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [?XMLATTR('var', ?NS_PUBSUB_s++"#"++Feature)]}
end, features(Host, Node))};
_ ->
node_disco_info(Host, Node, From)
end.
-iq_disco_items(Host, [], From) ->
+iq_disco_items(Host, [], From, _RSM) ->
{result, lists:map(
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
SN = node_to_string(SubNode),
@@ -1098,7 +928,7 @@
?XMLATTR('node', SN),
?XMLATTR('name', RN)]}
end, tree_action(Host, get_subnodes, [Host, [], From]))};
-iq_disco_items(Host, Item, From) ->
+iq_disco_items(Host, Item, From, RSM) ->
case string:tokens(Item, "!") of
[_SNode, _ItemID] ->
{result, []};
@@ -1110,9 +940,9 @@
%% TODO That is, remove name attribute (or node?, please check for 2.1)
Action =
fun(#pubsub_node{type = Type, id = NodeId}) ->
- NodeItems = case node_call(Type, get_items, [NodeId, From]) of
+ {NodeItems, RsmOut} = case node_call(Type, get_items, [NodeId, From, RSM]) of
{result, I} -> I;
- _ -> []
+ _ -> {[], none}
end,
Nodes = lists:map(
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
@@ -1128,7 +958,7 @@
#xmlel{ns = ?NS_DISCO_ITEMS, name = 'item', attrs = [?XMLATTR('jid', Host), ?XMLATTR('node', SN),
?XMLATTR('name', Name)]}
end, NodeItems),
- {result, Nodes ++ Items}
+ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
end,
case transaction(Host, Node, Action, sync_dirty) of
{result, {_, Result}} -> {result, Result};
@@ -1267,7 +1097,8 @@
(_, Acc) ->
Acc
end, [], exmpp_xml:remove_cdata_from_list(Els)),
- get_items(Host, Node, From, SubId, MaxItems, ItemIDs);
+ RSM = jlib:rsm_decode(SubEl),
+ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM);
{get, 'subscriptions'} ->
get_subscriptions(Host, Node, From, Plugins);
{get, 'affiliations'} ->
@@ -1289,8 +1120,11 @@
end.
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
- SubEls = SubEl#xmlel.children,
- Action = exmpp_xml:remove_cdata_from_list(SubEls),
+ SubEls = exmpp_xml:get_child_elements(SubEl),
+ NoRSM = lists:filter(fun(#xmlel{name = Name}) ->
+ Name == 'set'
+ end, SubEls),
+ Action = SubEls -- NoRSM, %%pablo why not doing it once on lists:filter?
case Action of
[#xmlel{name = Name, attrs = Attrs, children = Els}] ->
Node = case Host of
@@ -1420,7 +1254,8 @@
_ -> []
end
end,
- case transaction(fun () -> {result, lists:flatmap(Tr, Plugins)} end,
+ case transaction(Host,
+ fun () -> {result, lists:flatmap(Tr, Plugins)} end,
sync_dirty) of
{result, Res} -> Res;
Err -> Err
@@ -1466,7 +1301,7 @@
%%% authorization handling
-send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, Subscriber) ->
+send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = NodeId}, Subscriber) ->
Lang = "en", %% TODO fix
{U, S, R} = Subscriber,
Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children =
@@ -1496,7 +1331,7 @@
lists:foreach(fun(Owner) ->
{U, S, R} = Owner,
ejabberd_router ! {route, service_jid(Host), exmpp_jid:make(U, S, R), Stanza}
- end, Owners).
+ end, node_owners(Host, Type, NodeId)).
find_authorization_response(Packet) ->
Els = Packet#xmlel.children,
@@ -1559,8 +1394,8 @@
"true" -> true;
_ -> false
end,
- Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) ->
- IsApprover = lists:member(jlib:short_prepd_bare_jid(From), Owners),
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ IsApprover = lists:member(jlib:short_prepd_bare_jid(From), node_owners_call(Type, NodeId)),
{result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
if
not IsApprover ->
@@ -1750,7 +1585,7 @@
end,
Reply = #xmlel{ns = ?NS_PUBSUB, name = 'pubsub', children =
[#xmlel{ns = ?NS_PUBSUB, name = 'create', attrs = nodeAttr(Node)}]},
- case transaction(CreateNode, transaction) of
+ case transaction(Host, CreateNode, transaction) of
{result, {Result, broadcast}} ->
%%Lang = "en", %% TODO: fix
%%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
@@ -1866,7 +1701,7 @@
{undefined, undefined, undefined}
end,
SubId = uniqid(),
- Action = fun(#pubsub_node{options = Options, owners = [Owner|_], type = Type, id = NodeId}) ->
+ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) ->
Features = features(Type),
SubscribeFeature = lists:member("subscribe", Features),
OptionsFeature = lists:member("subscription-options", Features),
@@ -1885,9 +1720,13 @@
{"", "", ""} ->
{false, false};
_ ->
- {OU, OS, _} = Owner,
- get_roster_info(OU, OS,
- Subscriber, AllowedGroups)
+ case node_owners_call(Type, NodeId) of
+ [{OU, OS, _} | _] ->
+ get_roster_info(OU, OS,
+ Subscriber, AllowedGroups);
+ _ ->
+ {false, false}
+ end
end
end,
if
@@ -2212,7 +2051,7 @@
%% <p>The permission are not checked in this function.</p>
%% @todo We probably need to check that the user doing the query has the right
%% to read the items.
-get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) ->
+get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) ->
MaxItems =
if
SMaxItems == "" -> ?MAXITEMS;
@@ -2251,11 +2090,11 @@
node_call(Type, get_items,
[NodeId, From,
AccessModel, PresenceSubscription, RosterGroup,
- SubId])
+ SubId, RSM])
end
end,
case transaction(Host, Node, Action, sync_dirty) of
- {result, {_, Items}} ->
+ {result, {_, Items, RSMOut}} ->
SendItems = case ItemIDs of
[] ->
Items;
@@ -2268,7 +2107,7 @@
%% number of items sent to MaxItems:
{result, #xmlel{ns = ?NS_PUBSUB, name = 'pubsub', children =
[#xmlel{ns = ?NS_PUBSUB, name = 'items', attrs = nodeAttr(Node), children =
- itemsEls(lists:sublist(SendItems, MaxItems))}]}};
+ itemsEls(lists:sublist(SendItems, MaxItems))} | jlib:rsm_encode(RSMOut)]}};
Error ->
Error
end
@@ -2300,16 +2139,25 @@
%% @doc <p>Resend the items of a node to the user.</p>
%% @todo use cache-last-item feature
send_items(Host, Node, NodeId, Type, LJID, last) ->
- case get_cached_item(Host, NodeId) of
+ Stanza = case get_cached_item(Host, NodeId) of
undefined ->
- send_items(Host, Node, NodeId, Type, LJID, 1);
+ % special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc
+ ToSend = case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of
+ {result, []} -> [];
+ {result, Items} -> Items
+ end,
+ event_stanza([#xmlel{ns = ?NS_PUBSUB_EVENT,
+ name = 'items',
+ attrs = nodeAttr(Node),
+ children = itemsEls(ToSend)}]);
LastItem ->
- Stanza = event_stanza(
+ event_stanza(
[#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = nodeAttr(Node),
- children = itemsEls(LastItem)}]),
- {U, S, R} = LJID,
- ejabberd_router ! {route, service_jid(Host), exmpp_jid:make(U, S, R), Stanza}
- end;
+ children = itemsEls(LastItem)}])
+ end,
+ {U, S, R} = LJID,
+ ejabberd_router ! {route, service_jid(Host), exmpp_jid:make(U, S, R), Stanza};
+
send_items(Host, Node, NodeId, Type, {LU, LS, LR} = LJID, Number) ->
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
{result, []} ->
@@ -2430,29 +2278,12 @@
error ->
{error, 'bad-request'};
_ ->
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) ->
- case lists:member(Owner, Owners) of
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ case lists:member(Owner, node_owners_call(Type, NodeId)) of
true ->
lists:foreach(
fun({JID, Affiliation}) ->
- {result, _} = node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
- case Affiliation of
- owner ->
- NewOwner = jlib:short_prepd_bare_jid(JID),
- NewOwners = [NewOwner|Owners],
- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
- none ->
- OldOwner = jlib:short_prepd_bare_jid(JID),
- case lists:member(OldOwner, Owners) of
- true ->
- NewOwners = Owners--[OldOwner],
- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
- _ ->
- ok
- end;
- _ ->
- ok
- end
+ node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
end, Entities),
{result, []};
_ ->
@@ -2733,8 +2564,8 @@
ejabberd_router ! {route, service_jid(Host), JID, Stanza}
end,
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) ->
- case lists:member(Owner, Owners) of
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ case lists:member(Owner, node_owners_call(Type, NodeId)) of
true ->
Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
@@ -3237,6 +3068,30 @@
Result
end.
+%% @spec (NodeId) -> [ljid()]
+%% NodeId = pubsubNodeId()
+%% @doc <p>Return list of node owners.</p>
+node_owners(Host, Type, NodeId) ->
+ case node_action(Host, Type, get_node_affiliations, [NodeId]) of
+ {result, Affiliations} ->
+ lists:foldl(
+ fun({LJID, owner}, Acc) -> [LJID|Acc];
+ (_, Acc) -> Acc
+ end, [], Affiliations);
+ _ ->
+ []
+ end.
+node_owners_call(Type, NodeId) ->
+ case node_call(Type, get_node_affiliations, [NodeId]) of
+ {result, Affiliations} ->
+ lists:foldl(
+ fun({LJID, owner}, Acc) -> [LJID|Acc];
+ (_, Acc) -> Acc
+ end, [], Affiliations);
+ _ ->
+ []
+ end.
+
%% @spec (Host, Options) -> MaxItems
%% Host = host()
%% Options = [Option]
@@ -3613,7 +3468,13 @@
tree_action(Host, Function, Args) ->
?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
Fun = fun() -> tree_call(Host, Function, Args) end,
- catch mnesia:sync_dirty(Fun).
+ case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of
+ {atomic, Result} ->
+ Result;
+ {aborted, Reason} ->
+ ?ERROR_MSG("transaction return internal error: ~p~n",[{aborted, Reason}]),
+ {error, 'internal-server-error'}
+ end.
%% @doc <p>node plugin call.</p>
node_call(Type, Function, Args) ->
@@ -3633,13 +3494,13 @@
node_action(Host, Type, Function, Args) ->
?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
- transaction(fun() ->
+ transaction(Host, fun() ->
node_call(Type, Function, Args)
end, sync_dirty).
%% @doc <p>plugin transaction handling.</p>
transaction(Host, Node, Action, Trans) ->
- transaction(fun() ->
+ transaction(Host, fun() ->
case tree_call(Host, get_node, [Host, Node]) of
N when is_record(N, pubsub_node) ->
case Action(N) of
@@ -3652,8 +3513,15 @@
end
end, Trans).
-transaction(Fun, Trans) ->
- case catch mnesia:Trans(Fun) of
+transaction(Host, Fun, Trans) ->
+ transaction_retry(Host, Fun, Trans, 2).
+
+transaction_retry(Host, Fun, Trans, Count) ->
+ SqlFun = case Trans of
+ transaction -> sql_transaction;
+ _ -> sql_bloc
+ end,
+ case catch ejabberd_odbc:SqlFun(odbc_conn(Host), Fun) of
{result, Result} -> {result, Result};
{error, Error} -> {error, Error};
{atomic, {result, Result}} -> {result, Result};
@@ -3661,6 +3529,15 @@
{aborted, Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
{error, 'internal-server-error'};
+ {'EXIT', {timeout, _} = Reason} ->
+ case Count of
+ 0 ->
+ ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
+ {error, 'internal-server-error'};
+ N ->
+ erlang:yield(),
+ transaction_retry(Host, Fun, Trans, N-1)
+ end;
{'EXIT', Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
{error, 'internal-server-error'};
@@ -3669,6 +3546,16 @@
{error, 'internal-server-error'}
end.
+odbc_conn({_U, Host, _R})->
+ Host;
+odbc_conn(Host) ->
+ Host--"pubsub.". %% TODO, improve that for custom host
+
+%% escape value for database storage
+escape({_U, _H, _R}=JID)->
+ ejabberd_odbc:escape(exmpp_jid:to_list(JID));
+escape(Value)->
+ ejabberd_odbc:escape(Value).
%%%% helpers
%% Add pubsub-specific error element

View File

@ -0,0 +1,294 @@
%%% ====================================================================
%%% ``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 Pablo Polvorin <pablo.polvorin@process-one.net>
%%% @author based on pubsub_subscription.erl by Brian Cully <bjc@kublai.com>
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(pubsub_subscription_odbc).
-author("pablo.polvorin@process-one.net").
%% 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_lib("exmpp/include/exmpp.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").
-define(DB_MOD, pubsub_db_odbc).
%%====================================================================
%% API
%%====================================================================
init() ->
ok = create_table().
subscribe_node(_JID, _NodeID, Options) ->
SubId = make_subid(),
ok = ?DB_MOD:add_subscription(#pubsub_subscription{subid = SubId, options = Options}),
{result, SubId}.
unsubscribe_node(_JID, _NodeID, SubID) ->
{ok, Sub} = ?DB_MOD:read_subscription(SubID),
ok = ?DB_MOD:delete_subscription(SubID),
{result, Sub}.
get_subscription(_JID, _NodeID, SubID) ->
case ?DB_MOD:read_subscription(SubID) of
{ok, Sub} -> {result, Sub};
notfound -> {error, notfound}
end.
set_subscription(_JID, _NodeID, SubID, Options) ->
case ?DB_MOD:read_subscription(SubID) of
{ok, _} ->
ok = ?DB_MOD:update_subscription(#pubsub_subscription{subid = SubID, options = Options}),
{result, ok};
notfound ->
{error, notfound}
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{ns = ?NS_DATA_FORMS, name = 'x', children =
[#xmlel{ns = ?NS_DATA_FORMS,
name = 'field',
attrs = [?XMLATTR('var', <<"FORM_TYPE">>), ?XMLATTR('type', <<"hidden">>)],
children = [#xmlel{ns = ?NS_DATA_FORMS,
name = 'value',
children = [?XMLCDATA(?NS_PUBSUB_SUBSCRIBE_OPTIONS_s)]}]}] ++ XFields}}.
parse_options_xform(XFields) ->
case exmpp_xml:get_child_elements(XFields) of
[] -> {result, []};
[#xmlel{name = 'x'} = 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() ->
ok.
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, exmpp_stanza:error(?NS_JABBER_CLIENT, '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, exmpp_stanza:error(?NS_JABBER_CLIENT, '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,
#xmlel{ns = ?NS_DATA_FORMS,
name = 'field',
attrs = [?XMLATTR('var', Var), ?XMLATTR('type', Type), ?XMLATTR('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{ns = ?NS_DATA_FORMS,
name = 'option',
attrs = [?XMLATTR('label', transalte:translate(Lang, Label))],
children = [#xmlel{ns = ?NS_DATA_FORMS,
name = 'value',
children = [?XMLCDATA(Value)]}]}.
tr_xfield_values(Value) ->
#xmlel{ns = ?NS_DATA_FORMS, name ='value', children = [?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".