mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-22 16:20:52 +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:
parent
ba3a45452e
commit
944dd1cc7f
@ -25,7 +25,7 @@ ERLBEHAVBEAMS = $(addprefix $(OUTDIR)/,$(ERLBEHAVS:.erl=.beam))
|
|||||||
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
|
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
|
||||||
|
|
||||||
|
|
||||||
all: $(ERLBEHAVBEAMS) $(BEAMS)
|
all: mod_pubsub_odbc.erl $(ERLBEHAVBEAMS) $(BEAMS)
|
||||||
|
|
||||||
$(BEAMS): $(ERLBEHAVBEAMS)
|
$(BEAMS): $(ERLBEHAVBEAMS)
|
||||||
|
|
||||||
@ -38,6 +38,9 @@ clean:
|
|||||||
distclean: clean
|
distclean: clean
|
||||||
rm -f Makefile
|
rm -f Makefile
|
||||||
|
|
||||||
|
mod_pubsub_odbc.erl:
|
||||||
|
patch -o mod_pubsub_odbc.erl mod_pubsub.erl pubsub_odbc.patch
|
||||||
|
|
||||||
TAGS:
|
TAGS:
|
||||||
etags *.erl
|
etags *.erl
|
||||||
|
|
||||||
|
3599
src/mod_pubsub/mod_pubsub_odbc.erl
Normal file
3599
src/mod_pubsub/mod_pubsub_odbc.erl
Normal file
File diff suppressed because it is too large
Load Diff
183
src/mod_pubsub/node_flat_odbc.erl
Normal file
183
src/mod_pubsub/node_flat_odbc.erl
Normal 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).
|
1324
src/mod_pubsub/node_hometree_odbc.erl
Normal file
1324
src/mod_pubsub/node_hometree_odbc.erl
Normal file
File diff suppressed because it is too large
Load Diff
327
src/mod_pubsub/node_pep_odbc.erl
Normal file
327
src/mod_pubsub/node_pep_odbc.erl
Normal 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.
|
357
src/mod_pubsub/nodetree_tree_odbc.erl
Normal file
357
src/mod_pubsub/nodetree_tree_odbc.erl
Normal 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).
|
138
src/mod_pubsub/pubsub_db_odbc.erl
Normal file
138
src/mod_pubsub/pubsub_db_odbc.erl
Normal 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).
|
672
src/mod_pubsub/pubsub_odbc.patch
Normal file
672
src/mod_pubsub/pubsub_odbc.patch
Normal 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
|
294
src/mod_pubsub/pubsub_subscription_odbc.erl
Normal file
294
src/mod_pubsub/pubsub_subscription_odbc.erl
Normal 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".
|
Loading…
Reference in New Issue
Block a user