initial merge of pubsub odbc, compilation pass ok

SVN Revision: 2437
This commit is contained in:
Christophe Romain 2009-08-07 08:26:47 +00:00
parent 0ad45b1a93
commit 5598d34478
14 changed files with 6941 additions and 57 deletions

View File

@ -25,7 +25,7 @@ ERLBEHAVBEAMS = $(addprefix $(OUTDIR)/,$(ERLBEHAVS:.erl=.beam))
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
all: $(ERLBEHAVBEAMS) $(BEAMS)
all: odbc $(ERLBEHAVBEAMS) $(BEAMS)
$(BEAMS): $(ERLBEHAVBEAMS)
@ -38,6 +38,9 @@ clean:
distclean: clean
rm -f Makefile
odbc:
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,188 @@
%%% ====================================================================
%%% ``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("jlib.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/7,
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_subscription/2,
set_subscription/3,
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
]).
init(Host, ServerHost, Opts) ->
node_hometree_odbc:init(Host, ServerHost, Opts).
terminate(Host, ServerHost) ->
node_hometree_odbc:terminate(Host, ServerHost).
options() ->
[{node_type, flat},
{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:jid_tolower(Owner),
Allowed = case LOwner of
{"", Host, ""} ->
true; % pubsub service always allowed
_ ->
acl:match_rule(ServerHost, Access, LOwner) =:= 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) ->
node_hometree_odbc:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
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_entity_subscriptions_for_send_last(Host, Owner) ->
node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
get_node_subscriptions(NodeId) ->
node_hometree_odbc:get_node_subscriptions(NodeId).
get_subscription(NodeId, Owner) ->
node_hometree_odbc:get_subscription(NodeId, Owner).
set_subscription(NodeId, Owner, Subscription) ->
node_hometree_odbc:set_subscription(NodeId, Owner, Subscription).
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_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).

View File

@ -92,7 +92,7 @@
%% 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 = pubsub_subscription:init(),
pubsub_subscription:init(),
mnesia:create_table(pubsub_state,
[{disc_copies, [node()]},
{attributes, record_info(fields, pubsub_state)}]),
@ -311,8 +311,9 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
(AccessModel == whitelist) and (not Whitelisted) ->
%% Node has whitelist access model and entity lacks required affiliation
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
PendingSubscription ->
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
(AccessModel == authorize) -> % TODO: to be done
%% Node has authorize access model
{error, ?ERR_FORBIDDEN};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
@ -320,33 +321,32 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
%% % Requesting entity is anonymous
%% {error, ?ERR_FORBIDDEN};
true ->
case pubsub_subscription:subscribe_node(Subscriber,
NodeId, Options) of
{result, SubID} ->
case pubsub_subscription:subscribe_node(Subscriber, NodeId, Options) of
{result, SubId} ->
NewSub = case AccessModel of
authorize -> pending;
_ -> subscribed
_ -> subscribed
end,
set_state(SubState#pubsub_state{subscriptions = [{NewSub, SubID} | Subscriptions]}),
set_state(SubState#pubsub_state{subscriptions = [{NewSub, SubId} | Subscriptions]}),
case {NewSub, SendLast} of
{subscribed, never} ->
{result, {default, subscribed, SubID}};
{result, {default, subscribed, SubId}};
{subscribed, _} ->
{result, {default, subscribed, SubID, send_last}};
{result, {default, subscribed, SubId, send_last}};
{_, _} ->
{result, {default, pending, SubID}}
{result, {default, pending, SubId}}
end;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
end.
%% @spec (NodeId, Sender, Subscriber, SubID) ->
%% @spec (NodeId, Sender, Subscriber, SubId) ->
%% {error, Reason} | {result, []}
%% NodeId = mod_pubsub:pubsubNodeId()
%% Sender = mod_pubsub:jid()
%% Subscriber = mod_pubsub:jid()
%% SubID = mod_pubsub:subid()
%% SubId = mod_pubsub:subid()
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
@ -358,8 +358,8 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
GenKey -> GenState;
_ -> get_state(NodeId, SubKey)
end,
Subscriptions = lists:filter(fun({_Sub, _SubID}) -> true;
(_SubID) -> false
Subscriptions = lists:filter(fun({_Sub, _SubId}) -> true;
(_SubId) -> false
end, SubState#pubsub_state.subscriptions),
SubIdExists = case SubId of
[] -> false;
@ -370,11 +370,11 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
%% Requesting entity is prohibited from unsubscribing entity
not Authorized ->
{error, ?ERR_FORBIDDEN};
%% Entity did not specify SubID
%%SubID == "", ?? ->
%% Entity did not specify SubId
%%SubId == "", ?? ->
%% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%% Invalid subscription identifier
%%InvalidSubID ->
%%InvalidSubId ->
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
%% Requesting entity is not a subscriber
Subscriptions == [] ->
@ -405,11 +405,11 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}
end.
delete_subscription(SubKey, NodeID, {Subscription, SubID}, SubState) ->
delete_subscription(SubKey, NodeID, {Subscription, SubId}, SubState) ->
Affiliation = SubState#pubsub_state.affiliation,
AllSubs = SubState#pubsub_state.subscriptions,
NewSubs = AllSubs -- [{Subscription, SubID}],
pubsub_subscription:unsubscribe_node(SubKey, NodeID, SubID),
NewSubs = AllSubs -- [{Subscription, SubId}],
pubsub_subscription:unsubscribe_node(SubKey, NodeID, SubId),
case {Affiliation, NewSubs} of
{none, []} ->
% Just a regular subscriber, and this is final item, so
@ -662,8 +662,8 @@ get_entity_subscriptions(Host, Owner) ->
Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
case NodeTree:get_node(N) of
#pubsub_node{nodeid = {Host, _}} = Node ->
lists:foldl(fun({Sub, SubID}, Acc2) ->
[{Node, Sub, SubID, J} | Acc2];
lists:foldl(fun({Sub, SubId}, Acc2) ->
[{Node, Sub, SubId, J} | Acc2];
(S, Acc2) ->
[{Node, S, J} | Acc2]
end, Acc, Ss);
@ -678,8 +678,8 @@ get_node_subscriptions(NodeId) ->
%% TODO: get rid of cases to handle non-list subscriptions
case Subscriptions of
[_|_] ->
lists:foldl(fun({S, SubID}, Acc) ->
[{J, S, SubID} | Acc];
lists:foldl(fun({S, SubId}, Acc) ->
[{J, S, SubId} | Acc];
(S, Acc) ->
[{J, S} | Acc]
end, [], Subscriptions);
@ -696,19 +696,6 @@ get_subscriptions(NodeId, Owner) ->
SubState = get_state(NodeId, SubKey),
{result, SubState#pubsub_state.subscriptions}.
set_subscriptions(NodeId, Owner, none, SubId) ->
SubKey = jlib:jid_tolower(Owner),
SubState = get_state(NodeId, SubKey),
case {SubId, SubState#pubsub_state.subscriptions} of
{_, []} ->
{error, ?ERR_ITEM_NOT_FOUND};
{"", [{_, SID}]} ->
unsub_with_subid(NodeId, SID, SubState);
{"", [_|_]} ->
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
_ ->
unsub_with_subid(NodeId, SubId, SubState)
end;
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
SubKey = jlib:jid_tolower(Owner),
SubState = get_state(NodeId, SubKey),
@ -716,11 +703,17 @@ set_subscriptions(NodeId, Owner, Subscription, SubId) ->
{_, []} ->
{error, ?ERR_ITEM_NOT_FOUND};
{"", [{_, SID}]} ->
replace_subscription({Subscription, SID}, SubState);
case Subscription of
none -> unsub_with_subid(NodeId, SID, SubState);
_ -> replace_subscription({Subscription, SID}, SubState)
end;
{"", [_|_]} ->
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
_ ->
replace_subscription({Subscription, SubId}, SubState)
case Subscription of
none -> unsub_with_subid(NodeId, SubId, SubState);
_ -> replace_subscription({Subscription, SubId}, SubState)
end
end.
replace_subscription(NewSub, SubState) ->
@ -730,8 +723,8 @@ replace_subscription(NewSub, SubState) ->
replace_subscription(_, [], Acc) ->
Acc;
replace_subscription({Sub, SubID}, [{_, SubID} | T], Acc) ->
replace_subscription({Sub, SubID}, T, [{Sub, SubID} | Acc]).
replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) ->
replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]).
unsub_with_subid(NodeId, SubId, SubState) ->
pubsub_subscription:unsubscribe_node(SubState#pubsub_state.stateid,
@ -863,10 +856,10 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
Subscriptions = SubState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
if
%%SubID == "", ?? ->
%%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%%InvalidSubID ->
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
GenState#pubsub_state.affiliation == outcast ->
@ -881,7 +874,7 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
(AccessModel == whitelist) and (not Whitelisted) ->
%% Node has whitelist access model and entity lacks required affiliation
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
(AccessModel == authorize) and (not Whitelisted) ->
(AccessModel == authorize) -> % TODO: to be done
%% Node has authorize access model
{error, ?ERR_FORBIDDEN};
%%MustPay ->
@ -911,10 +904,10 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
Subscriptions = GenState#pubsub_state.subscriptions,
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
if
%%SubID == "", ?? ->
%%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%%InvalidSubID ->
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
GenState#pubsub_state.affiliation == outcast ->
@ -976,7 +969,7 @@ can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions);
can_fetch_item(_Affiliation, _Subscription) -> false.
is_subscribed(Subscriptions) ->
lists:any(fun ({subscribed, _SubID}) -> true;
lists:any(fun ({subscribed, _SubId}) -> true;
(_) -> false
end, Subscriptions).

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("ejabberd.hrl").
-include("pubsub.hrl").
-include("jlib.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:jid_tolower(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
{"", Host, ""} ->
true; % pubsub service always allowed
_ ->
case acl:match_rule(ServerHost, Access, LOwner) 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:jid_tolower(jlib:jid_remove_resource(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:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(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:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(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

@ -148,10 +148,8 @@ get_parentnodes(_Host, _Node, _From) ->
%% 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, mnesia:read(pubsub_node, {Host, Node})}];
Error ->
Error
N when is_record(N, pubsub_node) -> [{0, [N]}];
Error -> Error
end.
%% @spec (Host, Node, From) -> [pubsubNode()] | {error, Reason}

View File

@ -0,0 +1,353 @@
%%% ====================================================================
%%% ``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("pubsub.hrl").
-include("jlib.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, ?ERR_INTERNAL_SERVER_ERROR};
_ ->
{error, ?ERR_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, ?ERR_INTERNAL_SERVER_ERROR};
_ ->
{error, ?ERR_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.
%% @spec (Host, Index) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
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) -> [pubsubNode()] | {error, Reason}
%% Host = mod_pubsub:host()
%% Node = mod_pubsub:pubsubNode()
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, ?ERR_ITEM_NOT_FOUND} ->
{ParentNode, ParentExists} = case Host of
{_U, _S, _R} ->
%% This is special case for PEP handling
%% PEP does not uses hierarchy
{[], true};
_ ->
Parent = lists:sublist(Node, length(Node) - 1),
ParentE = (Parent == []) orelse
case nodeid(Host, Parent) of
{result, _} -> true;
_ -> false
end,
{Parent, ParentE}
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, ?ERR_FORBIDDEN}
end;
{result, _} ->
%% NodeID already exists
{error, ?ERR_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, ?ERR_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, ?ERR_INTERNAL_SERVER_ERROR};
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end.
string_to_node({_, _, _}, Node) -> Node;
string_to_node(_, Node) -> ?PUBSUB:string_to_node(Node).

View File

@ -0,0 +1,136 @@
%%% ====================================================================
%%% ``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]).
-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,484 @@
--- mod_pubsub.erl 2009-07-31 16:53:48.000000000 +0200
+++ mod_pubsub_odbc.erl 2009-07-31 17:07:00.000000000 +0200
@@ -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').
@@ -57,9 +57,9 @@
-include("jlib.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,
@@ -104,7 +104,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
@@ -123,7 +124,7 @@
-export([send_loop/1
]).
--define(PROCNAME, ejabberd_mod_pubsub).
+-define(PROCNAME, ejabberd_mod_pubsub_odbc).
-define(PLUGIN_PREFIX, "node_").
-define(TREE_PREFIX, "nodetree_").
@@ -212,8 +213,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,
@@ -456,17 +455,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
@@ -789,10 +786,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 ->
@@ -906,7 +903,8 @@
sub_el = SubEl} = IQ ->
{xmlelement, _, QAttrs, _} = SubEl,
Node = xml:get_attr_s("node", QAttrs),
- 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} ->
jlib:iq_to_xml(
IQ#iq{type = result,
@@ -1011,7 +1009,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"];
_ -> []
@@ -1027,8 +1025,9 @@
[];
true ->
[{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
- lists:map(fun(T) ->
- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
+ lists:map(fun
+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
end, features(Type))]
end,
%% TODO: add meta-data info (spec section 5.4)
@@ -1056,14 +1055,15 @@
{xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
- lists:map(fun(Feature) ->
- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []}
+ lists:map(fun
+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
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),
@@ -1073,7 +1073,7 @@
{"node", SN},
{"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, []};
@@ -1085,9 +1085,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}}) ->
@@ -1103,7 +1103,7 @@
{xmlelement, "item", [{"jid", Host}, {"node", SN},
{"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};
@@ -1235,7 +1235,8 @@
(_, Acc) ->
Acc
end, [], xml:remove_cdata(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"} ->
@@ -1258,7 +1259,10 @@
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
{xmlelement, _, _, SubEls} = SubEl,
- Action = xml:remove_cdata(SubEls),
+ NoRSM = lists:filter(fun({xmlelement, Name, _, _}) ->
+ Name == "set"
+ end, SubEls),
+ Action = xml:remove_cdata(SubEls) -- NoRSM,
case Action of
[{xmlelement, Name, Attrs, Els}] ->
Node = case Host of
@@ -1384,7 +1388,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
@@ -1428,7 +1433,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
Stanza = {xmlelement, "message",
[],
@@ -1457,7 +1462,7 @@
[{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
lists:foreach(fun(Owner) ->
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(Owner), Stanza}
- end, Owners).
+ end, node_owners(Host, Type, NodeId)).
find_authorization_response(Packet) ->
{xmlelement, _Name, _Attrs, Els} = Packet,
@@ -1524,8 +1529,8 @@
"true" -> true;
_ -> false
end,
- Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) ->
- IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), Owners),
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
+ IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), node_owners_call(Type, NodeId)),
{result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
if
not IsApprover ->
@@ -1711,7 +1716,7 @@
Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
[{xmlelement, "create", 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)),
@@ -1823,7 +1828,7 @@
error -> {"", "", ""};
J -> jlib:jid_tolower(J)
end,
- 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),
@@ -1842,9 +1847,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
@@ -2167,7 +2176,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;
@@ -2206,11 +2215,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;
@@ -2223,7 +2232,8 @@
%% number of items sent to MaxItems:
{result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
[{xmlelement, "items", nodeAttr(Node),
- itemsEls(lists:sublist(SendItems, MaxItems))}]}]};
+ itemsEls(lists:sublist(SendItems, MaxItems))}
+ | jlib:rsm_encode(RSMOut)]}]};
Error ->
Error
end
@@ -2255,15 +2265,22 @@
%% @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(
+ [{xmlelement, "items", nodeAttr(Node),
+ itemsEls(ToSend)}]);
LastItem ->
- Stanza = event_stanza(
+ event_stanza(
[{xmlelement, "items", nodeAttr(Node),
- itemsEls([LastItem])}]),
- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza}
- end;
+ itemsEls([LastItem])}])
+ end,
+ ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza};
send_items(Host, Node, NodeId, Type, LJID, Number) ->
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
{result, []} ->
@@ -2381,29 +2398,12 @@
error ->
{error, ?ERR_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}) ->
- node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
- case Affiliation of
- owner ->
- NewOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
- NewOwners = [NewOwner|Owners],
- tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]);
- none ->
- OldOwner = jlib:jid_tolower(jlib:jid_remove_resource(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, []};
_ ->
@@ -2665,8 +2665,8 @@
error ->
{error, ?ERR_BAD_REQUEST};
_ ->
- 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 ->
lists:foreach(fun({JID, Subscription, SubId}) ->
node_call(Type, set_subscriptions, [NodeId, JID, Subscription, SubId])
@@ -3154,6 +3154,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 (Options) -> MaxItems
%% Host = host()
%% Options = [Option]
@@ -3527,7 +3551,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, ?ERR_INTERNAL_SERVER_ERROR}
+ end.
%% @doc <p>node plugin call.</p>
node_call(Type, Function, Args) ->
@@ -3547,13 +3577,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
@@ -3566,8 +3596,14 @@
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};
@@ -3575,6 +3611,15 @@
{aborted, Reason} ->
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
{error, ?ERR_INTERNAL_SERVER_ERROR};
+ {'EXIT', {timeout, _} = Reason} ->
+ case Count of
+ 0 ->
+ ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
+ {error, ?ERR_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, ?ERR_INTERNAL_SERVER_ERROR};
@@ -3583,6 +3628,17 @@
{error, ?ERR_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(jlib:jid_to_string(JID));
+escape(Value)->
+ ejabberd_odbc:escape(Value).
+
%%%% helpers
%% Add pubsub-specific error element

View File

@ -86,28 +86,28 @@ init() ->
ok = create_table().
subscribe_node(JID, NodeID, Options) ->
case mnesia:transaction(fun add_subscription/3,
case mnesia:sync_dirty(fun add_subscription/3,
[JID, NodeID, Options]) of
{atomic, Result} -> {result, Result};
{aborted, Error} -> Error
end.
unsubscribe_node(JID, NodeID, SubID) ->
case mnesia:transaction(fun delete_subscription/3,
case mnesia:sync_dirty(fun delete_subscription/3,
[JID, NodeID, SubID]) of
{atomic, Result} -> {result, Result};
{aborted, Error} -> Error
end.
get_subscription(JID, NodeID, SubID) ->
case mnesia:transaction(fun read_subscription/3,
case mnesia:sync_dirty(fun read_subscription/3,
[JID, NodeID, SubID]) of
{atomic, Result} -> {result, Result};
{aborted, Error} -> Error
end.
set_subscription(JID, NodeID, SubID, Options) ->
case mnesia:transaction(fun write_subscription/4,
case mnesia:sync_dirty(fun write_subscription/4,
[JID, NodeID, SubID, Options]) of
{atomic, Result} -> {result, Result};
{aborted, Error} -> Error

View File

@ -0,0 +1,292 @@
%%% ====================================================================
%%% ``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>, based on
%% pubsub_subscription.erl by Brian Cully <bjc@kublai.com>
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(pubsub_subscription_odbc).
-author("bjc@kublai.com").
%% API
-export([init/0,
subscribe_node/3,
unsubscribe_node/3,
get_subscription/3,
set_subscription/4,
get_options_xform/2,
parse_options_xform/1]).
-include_lib("stdlib/include/qlc.hrl").
-include("pubsub.hrl").
-include("jlib.hrl").
-define(PUBSUB_DELIVER, "pubsub#deliver").
-define(PUBSUB_DIGEST, "pubsub#digest").
-define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency").
-define(PUBSUB_EXPIRE, "pubsub#expire").
-define(PUBSUB_INCLUDE_BODY, "pubsub#include_body").
-define(PUBSUB_SHOW_VALUES, "pubsub#show-values").
-define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type").
-define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth").
-define(DELIVER_LABEL,
"Whether an entity wants to receive or disable notifications").
-define(DIGEST_LABEL,
"Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually").
-define(DIGEST_FREQUENCY_LABEL,
"The minimum number of milliseconds between sending any two notification digests").
-define(EXPIRE_LABEL,
"The DateTime at which a leased subscription will end or has ended").
-define(INCLUDE_BODY_LABEL,
"Whether an entity wants to receive an XMPP message body in addition to the payload format").
-define(SHOW_VALUES_LABEL,
"The presence states for which an entity wants to receive notifications").
-define(SUBSCRIPTION_TYPE_LABEL,
"Type of notification to receive").
-define(SUBSCRIPTION_DEPTH_LABEL,
"Depth from subscription for which to receive notifications").
-define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away").
-define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat").
-define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)").
-define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)").
-define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)").
-define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL,
"Receive notification of new items only").
-define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL,
"Receive notification of new nodes only").
-define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL,
"Receive notification from direct child nodes only").
-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL,
"Receive notification from all descendent nodes").
-define(DB_MOD, pubsub_db_odbc).
%%====================================================================
%% API
%%====================================================================
init() ->
ok = create_table().
subscribe_node(_JID, _NodeID, Options) ->
SubId = make_subid(),
ok = pubsub_db_odbc: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, {xmlelement, "x", [{"xmlns", ?NS_XDATA}],
[{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
[{xmlelement, "value", [],
[{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] ++ XFields}}.
parse_options_xform(XFields) ->
case xml:remove_cdata(XFields) of
[] -> {result, []};
[{xmlelement, "x", _Attrs, _Els} = XEl] ->
case jlib:parse_xdata_submit(XEl) of
XData when is_list(XData) ->
case set_xoption(XData, []) of
Opts when is_list(Opts) -> {result, Opts};
Other -> Other
end;
Other ->
Other
end;
Other ->
Other
end.
%%====================================================================
%% Internal functions
%%====================================================================
create_table() ->
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, ?ERR_NOT_ACCEPTABLE}
end.
%% Convert XForm booleans to Erlang booleans.
xopt_to_bool("0") -> false;
xopt_to_bool("1") -> true;
xopt_to_bool("false") -> false;
xopt_to_bool("true") -> true;
xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}.
%% Return a field for an XForm for Key, with data filled in, if
%% applicable, from Options.
get_option_xfield(Lang, Key, Options) ->
Var = xfield_var(Key),
Label = xfield_label(Key),
{Type, OptEls} = type_and_options(xfield_type(Key), Lang),
Vals = case lists:keysearch(Key, 1, Options) of
{value, {_, Val}} ->
[tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)];
false ->
[]
end,
{xmlelement, "field",
[{"var", Var}, {"type", Type},
{"label", translate:translate(Lang, Label)}],
OptEls ++ Vals}.
type_and_options({Type, Options}, Lang) ->
{Type, [tr_xfield_options(O, Lang) || O <- Options]};
type_and_options(Type, _Lang) ->
{Type, []}.
tr_xfield_options({Value, Label}, Lang) ->
{xmlelement, "option",
[{"label", translate:translate(Lang, Label)}], [{xmlelement, "value", [],
[{xmlcdata, Value}]}]}.
tr_xfield_values(Value) ->
{xmlelement, "value", [], [{xmlcdata, Value}]}.
%% Return the XForm variable name for a subscription option key.
xfield_var(deliver) -> ?PUBSUB_DELIVER;
xfield_var(digest) -> ?PUBSUB_DIGEST;
xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY;
xfield_var(expire) -> ?PUBSUB_EXPIRE;
xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY;
xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES;
xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE;
xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH.
%% Return the XForm variable type for a subscription option key.
xfield_type(deliver) -> "boolean";
xfield_type(digest) -> "boolean";
xfield_type(digest_frequency) -> "text-single";
xfield_type(expire) -> "text-single";
xfield_type(include_body) -> "boolean";
xfield_type(show_values) ->
{"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL},
{"chat", ?SHOW_VALUE_CHAT_LABEL},
{"dnd", ?SHOW_VALUE_DND_LABEL},
{"online", ?SHOW_VALUE_ONLINE_LABEL},
{"xa", ?SHOW_VALUE_XA_LABEL}]};
xfield_type(subscription_type) ->
{"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL},
{"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]};
xfield_type(subscription_depth) ->
{"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL},
{"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}.
%% Return the XForm variable label for a subscription option key.
xfield_label(deliver) -> ?DELIVER_LABEL;
xfield_label(digest) -> ?DIGEST_LABEL;
xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL;
xfield_label(expire) -> ?EXPIRE_LABEL;
xfield_label(include_body) -> ?INCLUDE_BODY_LABEL;
xfield_label(show_values) -> ?SHOW_VALUES_LABEL;
xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL;
xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL.
%% Return the XForm value for a subscription option key.
xfield_val(deliver, Val) -> [bool_to_xopt(Val)];
xfield_val(digest, Val) -> [bool_to_xopt(Val)];
xfield_val(digest_frequency, Val) -> [integer_to_list(Val)];
xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)];
xfield_val(include_body, Val) -> [bool_to_xopt(Val)];
xfield_val(show_values, Val) -> Val;
xfield_val(subscription_type, items) -> ["items"];
xfield_val(subscription_type, nodes) -> ["nodes"];
xfield_val(subscription_depth, all) -> ["all"];
xfield_val(subscription_depth, N) -> [integer_to_list(N)].
%% Convert erlang booleans to XForms.
bool_to_xopt(false) -> "false";
bool_to_xopt(true) -> "true".

View File

@ -158,3 +158,58 @@ CREATE TABLE roster_version (
-- ALTER TABLE rosterusers ADD COLUMN askmessage text AFTER ask;
-- UPDATE rosterusers SET askmessage = '';
-- ALTER TABLE rosterusers ALTER COLUMN askmessage SET NOT NULL;
CREATE TABLE pubsub_node (
host text,
node text,
parent text,
type text,
nodeid bigint auto_increment primary key
) CHARACTER SET utf8;
CREATE INDEX i_pubsub_node_parent ON pubsub_node(parent(120));
CREATE UNIQUE INDEX i_pubsub_node_tuple ON pubsub_node(host(20), node(120));
CREATE TABLE pubsub_node_option (
nodeid bigint,
name text,
val text
) CHARACTER SET utf8;
CREATE INDEX i_pubsub_node_option_nodeid ON pubsub_node_option(nodeid);
ALTER TABLE `pubsub_node_option` ADD FOREIGN KEY (`nodeid`) REFERENCES `ejabberd`.`pubsub_node` (`nodeid`) ON DELETE CASCADE;
CREATE TABLE pubsub_node_owner (
nodeid bigint,
owner text
) CHARACTER SET utf8;
CREATE INDEX i_pubsub_node_owner_nodeid ON pubsub_node_owner(nodeid);
ALTER TABLE `pubsub_node_owner` ADD FOREIGN KEY (`nodeid`) REFERENCES `ejabberd`.`pubsub_node` (`nodeid`) ON DELETE CASCADE;
CREATE TABLE pubsub_state (
nodeid bigint,
jid text,
affiliation character(1),
subscription character(1),
stateid bigint auto_increment primary key
) CHARACTER SET utf8;
CREATE INDEX i_pubsub_state_jid ON pubsub_state(jid(60));
CREATE UNIQUE INDEX i_pubsub_state_tuple ON pubsub_state(nodeid, jid(60));
ALTER TABLE `pubsub_state` ADD FOREIGN KEY (`nodeid`) REFERENCES `ejabberd`.`pubsub_node` (`nodeid`) ON DELETE CASCADE;
CREATE TABLE pubsub_item (
nodeid bigint,
itemid text,
publisher text,
creation text,
modification text,
payload text
) CHARACTER SET utf8;
CREATE INDEX i_pubsub_item_itemid ON pubsub_item(itemid(36));
CREATE UNIQUE INDEX i_pubsub_item_tuple ON pubsub_item(nodeid, itemid(36));
ALTER TABLE `pubsub_item` ADD FOREIGN KEY (`nodeid`) REFERENCES `ejabberd`.`pubsub_node` (`nodeid`) ON DELETE CASCADE;
CREATE TABLE pubsub_subscription_opt (
subid text,
opt_name varchar(32),
opt_value text
);
CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt(subid(32), opt_name(32));

View File

@ -162,3 +162,54 @@ CREATE TABLE roster_version (
-- ALTER TABLE rosterusers ADD COLUMN askmessage text;
-- UPDATE rosterusers SET askmessage = '';
-- ALTER TABLE rosterusers ALTER COLUMN askmessage SET NOT NULL;
CREATE TABLE pubsub_node (
host text,
node text,
parent text,
"type" text,
nodeid SERIAL UNIQUE
);
CREATE INDEX i_pubsub_node_parent ON pubsub_node USING btree (parent);
CREATE UNIQUE INDEX i_pubsub_node_tuple ON pubsub_node USING btree (host, node);
CREATE TABLE pubsub_node_option (
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
name text,
val text
);
CREATE INDEX i_pubsub_node_option_nodeid ON pubsub_node_option USING btree (nodeid);
CREATE TABLE pubsub_node_owner (
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
owner text
);
CREATE INDEX i_pubsub_node_owner_nodeid ON pubsub_node_owner USING btree (nodeid);
CREATE TABLE pubsub_state (
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
jid text,
affiliation character(1),
subscription character(1),
stateid SERIAL UNIQUE
);
CREATE INDEX i_pubsub_state_jid ON pubsub_state USING btree (jid);
CREATE UNIQUE INDEX i_pubsub_state_tuple ON pubsub_state USING btree (nodeid, jid);
CREATE TABLE pubsub_item (
nodeid bigint REFERENCES pubsub_node(nodeid) ON DELETE CASCADE,
itemid text,
publisher text,
creation text,
modification text,
payload text
);
CREATE INDEX i_pubsub_item_itemid ON pubsub_item USING btree (itemid);
CREATE UNIQUE INDEX i_pubsub_item_tuple ON pubsub_item USING btree (nodeid, itemid);
CREATE TABLE pubsub_subscription_opt (
subid text,
opt_name varchar(32),
opt_value text
);
CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt USING btree (subid, opt_name);